Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d71fc4c1c0 | |||
| a118d19393 | |||
| c6aa1e5c53 | |||
| ff577391f2 | |||
| ee0d5f9b97 | |||
| 391bd818ea | |||
| 43636aed99 | |||
| 8f8ccc945e | |||
| 409ec99397 | |||
| dc39a61e2c | |||
| eba9eac8a8 | |||
| ab6b52a0b4 | |||
| 2baa0b9a41 | |||
| 6a8b2461cd | |||
| bcb356fe69 | |||
| dd7827059a | |||
| 208e36c899 | |||
| b97ce74d1f | |||
| 155a449c4e | |||
| 4696fd6833 | |||
| 581a351fb1 | |||
| 8ce8656de2 | |||
| 1e49560f1f | |||
| e8f0b5a9de | |||
| 40287c4cfc | |||
| 0481bea44d | |||
| 9d565ca080 | |||
| 4773dd0aa2 | |||
| 6b9d9e6c4a | |||
| b4967af13e | |||
| e3dabe3e08 | |||
| 0a0a2bcb44 | |||
| 2b2a1246e7 | |||
| f78da81aa4 | |||
| 2597a092bb | |||
| 5c41c66a0f |
@@ -214,9 +214,18 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
run: |
|
run: |
|
||||||
|
# Fail loudly: previously this step had no `set -e`, so an auth or
|
||||||
|
# upload failure was swallowed (step exited 0 on the trailing echo)
|
||||||
|
# and the SDK silently never published. Surface failures now.
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "${GCP_SA_KEY:-}" ]; then
|
||||||
|
echo "FATAL: GCP_SA_KEY secret is empty — cannot authenticate to publish" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||||
gcloud config set project neuron-785695
|
gcloud config set project neuron-785695
|
||||||
|
echo "Publishing as active account: $(gcloud config get-value account 2>/dev/null)"
|
||||||
|
|
||||||
VERSION="${GITHUB_SHA:0:8}"
|
VERSION="${GITHUB_SHA:0:8}"
|
||||||
|
|
||||||
@@ -268,6 +277,12 @@ jobs:
|
|||||||
# Patches ci-base:dev in-place: pulls the existing image (which has all
|
# Patches ci-base:dev in-place: pulls the existing image (which has all
|
||||||
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
||||||
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
||||||
|
#
|
||||||
|
# continue-on-error: this is a CI-cache optimization, NOT the release
|
||||||
|
# artifact. It runs Docker (pull/build/push ~600MB) on the host-mode GCE
|
||||||
|
# runner where DinD/Docker availability is fragile. A failure here must
|
||||||
|
# never block or redden the job — the SDK publish above is the deliverable.
|
||||||
|
continue-on-error: true
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
|
|||||||
@@ -212,12 +212,21 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
run: |
|
run: |
|
||||||
|
# Fail loudly: previously this step had no `set -e`, so an auth or
|
||||||
|
# upload failure was swallowed (step exited 0 on the trailing echo)
|
||||||
|
# and the SDK silently never published. Surface failures now.
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "${GCP_SA_KEY:-}" ]; then
|
||||||
|
echo "FATAL: GCP_SA_KEY secret is empty — cannot authenticate to publish" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||||
apt-get install -y -qq apt-transport-https ca-certificates curl
|
apt-get install -y -qq apt-transport-https ca-certificates curl
|
||||||
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list
|
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||||
apt-get update -qq && apt-get install -y google-cloud-cli
|
apt-get update -qq && apt-get install -y google-cloud-cli
|
||||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||||
gcloud config set project neuron-785695
|
gcloud config set project neuron-785695
|
||||||
|
echo "Publishing as active account: $(gcloud config get-value account 2>/dev/null)"
|
||||||
|
|
||||||
VERSION="${GITHUB_SHA:0:8}"
|
VERSION="${GITHUB_SHA:0:8}"
|
||||||
|
|
||||||
@@ -253,6 +262,12 @@ jobs:
|
|||||||
# Patches ci-base:stage in-place: pulls the existing image (which has all
|
# Patches ci-base:stage in-place: pulls the existing image (which has all
|
||||||
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
||||||
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
||||||
|
#
|
||||||
|
# continue-on-error: this is a CI-cache optimization, NOT the release
|
||||||
|
# artifact. It runs Docker (pull/build/push ~600MB) on the host-mode GCE
|
||||||
|
# runner where DinD/Docker availability is fragile. A failure here must
|
||||||
|
# never block or redden the job — the SDK publish above is the deliverable.
|
||||||
|
continue-on-error: true
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
|
|||||||
@@ -288,12 +288,21 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
run: |
|
run: |
|
||||||
|
# Fail loudly: previously this step had no `set -e`, so an auth or
|
||||||
|
# upload failure was swallowed (step exited 0 on the trailing echo)
|
||||||
|
# and the SDK silently never published. Surface failures now.
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "${GCP_SA_KEY:-}" ]; then
|
||||||
|
echo "FATAL: GCP_SA_KEY secret is empty — cannot authenticate to publish" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||||
apt-get install -y -qq apt-transport-https ca-certificates curl
|
apt-get install -y -qq apt-transport-https ca-certificates curl
|
||||||
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list
|
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||||
apt-get update -qq && apt-get install -y google-cloud-cli
|
apt-get update -qq && apt-get install -y google-cloud-cli
|
||||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||||
gcloud config set project neuron-785695
|
gcloud config set project neuron-785695
|
||||||
|
echo "Publishing as active account: $(gcloud config get-value account 2>/dev/null)"
|
||||||
|
|
||||||
VERSION="${GITHUB_SHA:0:8}"
|
VERSION="${GITHUB_SHA:0:8}"
|
||||||
|
|
||||||
@@ -345,6 +354,12 @@ jobs:
|
|||||||
# Patches ci-base:latest in-place: pulls the existing image (which has all
|
# Patches ci-base:latest in-place: pulls the existing image (which has all
|
||||||
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
||||||
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
||||||
|
#
|
||||||
|
# continue-on-error: this is a CI-cache optimization, NOT the release
|
||||||
|
# artifact. It runs Docker (pull/build/push ~600MB) on the host-mode GCE
|
||||||
|
# runner where DinD/Docker availability is fragile. A failure here must
|
||||||
|
# never block or redden the job — the SDK publish above is the deliverable.
|
||||||
|
continue-on-error: true
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
env:
|
env:
|
||||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
# Link to produce the engram binary
|
# Link to produce the engram binary
|
||||||
- name: Link engram binary
|
- name: Link engram binary
|
||||||
run: |
|
run: |
|
||||||
cc -std=c11 -O2 \
|
cc -std=c11 -O2 -DHAVE_CURL \
|
||||||
-I /usr/local/lib/el \
|
-I /usr/local/lib/el \
|
||||||
-o dist/engram \
|
-o dist/engram \
|
||||||
dist/engram.c \
|
dist/engram.c \
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
# Link to produce the engram binary
|
# Link to produce the engram binary
|
||||||
- name: Link engram binary
|
- name: Link engram binary
|
||||||
run: |
|
run: |
|
||||||
cc -std=c11 -O2 \
|
cc -std=c11 -O2 -DHAVE_CURL \
|
||||||
-I /usr/local/lib/el \
|
-I /usr/local/lib/el \
|
||||||
-o dist/engram \
|
-o dist/engram \
|
||||||
dist/engram.c \
|
dist/engram.c \
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
# Link to produce the engram binary
|
# Link to produce the engram binary
|
||||||
- name: Link engram binary
|
- name: Link engram binary
|
||||||
run: |
|
run: |
|
||||||
cc -std=c11 -O2 \
|
cc -std=c11 -O2 -DHAVE_CURL \
|
||||||
-I /usr/local/lib/el \
|
-I /usr/local/lib/el \
|
||||||
-o dist/engram \
|
-o dist/engram \
|
||||||
dist/engram.c \
|
dist/engram.c \
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
+142
-95
@@ -10,6 +10,7 @@ el_val_t query_param(el_val_t path, el_val_t key);
|
|||||||
el_val_t query_int(el_val_t path, el_val_t key, el_val_t default_val);
|
el_val_t query_int(el_val_t path, el_val_t key, el_val_t default_val);
|
||||||
el_val_t extract_id(el_val_t path, el_val_t prefix);
|
el_val_t extract_id(el_val_t path, el_val_t prefix);
|
||||||
el_val_t route_stats(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_stats(el_val_t method, el_val_t path, el_val_t body);
|
||||||
|
el_val_t persist_canonical(void);
|
||||||
el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_get_node(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_get_node(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_scan_nodes(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_scan_nodes(el_val_t method, el_val_t path, el_val_t body);
|
||||||
@@ -20,18 +21,23 @@ el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body);
|
|||||||
el_val_t route_neighbors(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_neighbors(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_strengthen(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_strengthen(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_forget(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_forget(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_create_ise(el_val_t method, el_val_t path, el_val_t body);
|
|
||||||
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body);
|
|
||||||
el_val_t route_save(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_save(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_load(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_load(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t route_health(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t route_health(el_val_t method, el_val_t path, el_val_t body);
|
||||||
|
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body);
|
||||||
|
el_val_t route_load_merge(el_val_t method, el_val_t path, el_val_t body);
|
||||||
|
el_val_t route_emit_ise(el_val_t method, el_val_t path, el_val_t body);
|
||||||
|
el_val_t route_capture_knowledge(el_val_t method, el_val_t path, el_val_t body);
|
||||||
el_val_t check_auth_ok(el_val_t method, el_val_t body);
|
el_val_t check_auth_ok(el_val_t method, el_val_t body);
|
||||||
el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body);
|
el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body);
|
||||||
|
|
||||||
|
el_val_t bind_raw;
|
||||||
el_val_t bind_str;
|
el_val_t bind_str;
|
||||||
el_val_t port;
|
el_val_t port;
|
||||||
|
el_val_t data_dir_raw;
|
||||||
el_val_t data_dir;
|
el_val_t data_dir;
|
||||||
el_val_t snapshot_path;
|
el_val_t snapshot_path;
|
||||||
|
el_val_t boot_snap;
|
||||||
|
|
||||||
el_val_t parse_port(el_val_t bind) {
|
el_val_t parse_port(el_val_t bind) {
|
||||||
el_val_t colon = str_index_of(bind, EL_STR(":"));
|
el_val_t colon = str_index_of(bind, EL_STR(":"));
|
||||||
@@ -110,17 +116,22 @@ el_val_t route_stats(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
el_val_t persist_canonical(void) {
|
||||||
|
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
|
el_val_t dir = ({ el_val_t _if_result_1 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_1 = (EL_STR("/tmp/engram")); } else { _if_result_1 = (dir_raw); } _if_result_1; });
|
||||||
|
engram_save(el_str_concat(dir, EL_STR("/snapshot.json")));
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_create_node(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t content = json_get_string(body, EL_STR("content"));
|
el_val_t content = json_get_string(body, EL_STR("content"));
|
||||||
el_val_t node_type = json_get_string(body, EL_STR("node_type"));
|
el_val_t nt_raw = json_get_string(body, EL_STR("node_type"));
|
||||||
if (str_eq(node_type, EL_STR(""))) {
|
el_val_t node_type = ({ el_val_t _if_result_2 = 0; if (str_eq(nt_raw, EL_STR(""))) { _if_result_2 = (EL_STR("Memory")); } else { _if_result_2 = (nt_raw); } _if_result_2; });
|
||||||
node_type = EL_STR("Memory");
|
el_val_t sal_raw = json_get_float(body, EL_STR("salience"));
|
||||||
}
|
el_val_t salience = ({ el_val_t _if_result_3 = 0; if ((sal_raw == el_from_float(0.0))) { _if_result_3 = (el_from_float(0.5)); } else { _if_result_3 = (sal_raw); } _if_result_3; });
|
||||||
el_val_t salience = json_get_float(body, EL_STR("salience"));
|
|
||||||
if (salience == el_from_float(0.0)) {
|
|
||||||
salience = el_from_float(0.5);
|
|
||||||
}
|
|
||||||
el_val_t id = engram_node(content, node_type, salience);
|
el_val_t id = engram_node(content, node_type, salience);
|
||||||
|
el_val_t saved = persist_canonical();
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), id), EL_STR("\",\"content\":\"")), content), EL_STR("\",\"node_type\":\"")), node_type), EL_STR("\"}"));
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), id), EL_STR("\",\"content\":\"")), content), EL_STR("\",\"node_type\":\"")), node_type), EL_STR("\"}"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -146,11 +157,9 @@ el_val_t route_scan_nodes(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_scan_edges(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_scan_edges(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR"));
|
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
if (str_eq(dir, EL_STR(""))) {
|
el_val_t dir = ({ el_val_t _if_result_4 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_4 = (EL_STR("/tmp/engram")); } else { _if_result_4 = (dir_raw); } _if_result_4; });
|
||||||
dir = EL_STR("/tmp/engram");
|
el_val_t snap_path = el_str_concat(dir, EL_STR("/.scan-export.json"));
|
||||||
}
|
|
||||||
el_val_t snap_path = el_str_concat(dir, EL_STR("/snapshot.json"));
|
|
||||||
engram_save(snap_path);
|
engram_save(snap_path);
|
||||||
el_val_t snap = fs_read(snap_path);
|
el_val_t snap = fs_read(snap_path);
|
||||||
if (str_eq(snap, EL_STR(""))) {
|
if (str_eq(snap, EL_STR(""))) {
|
||||||
@@ -165,36 +174,22 @@ el_val_t route_scan_edges(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_search(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_search(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t q = EL_STR("");
|
el_val_t q = ({ el_val_t _if_result_5 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_5 = (query_param(path, EL_STR("q"))); } else { _if_result_5 = (json_get_string(body, EL_STR("query"))); } _if_result_5; });
|
||||||
if (str_eq(method, EL_STR("GET"))) {
|
el_val_t lim_url = query_int(path, EL_STR("limit"), 0);
|
||||||
q = query_param(path, EL_STR("q"));
|
el_val_t lim_body = json_get_int(body, EL_STR("limit"));
|
||||||
} else {
|
el_val_t lim_either = ({ el_val_t _if_result_6 = 0; if ((lim_url > 0)) { _if_result_6 = (lim_url); } else { _if_result_6 = (lim_body); } _if_result_6; });
|
||||||
q = json_get_string(body, EL_STR("query"));
|
el_val_t limit = ({ el_val_t _if_result_7 = 0; if ((lim_either > 0)) { _if_result_7 = (lim_either); } else { _if_result_7 = (20); } _if_result_7; });
|
||||||
}
|
|
||||||
el_val_t limit = query_int(path, EL_STR("limit"), 20);
|
|
||||||
if (limit == 0) {
|
|
||||||
limit = json_get_int(body, EL_STR("limit"));
|
|
||||||
}
|
|
||||||
if (limit == 0) {
|
|
||||||
limit = 20;
|
|
||||||
}
|
|
||||||
return engram_search_json(q, limit);
|
return engram_search_json(q, limit);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_activate(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_activate(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t q = EL_STR("");
|
el_val_t q = ({ el_val_t _if_result_8 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_8 = (query_param(path, EL_STR("q"))); } else { _if_result_8 = (json_get_string(body, EL_STR("query"))); } _if_result_8; });
|
||||||
el_val_t depth = 3;
|
if (str_eq(q, EL_STR(""))) {
|
||||||
if (str_eq(method, EL_STR("GET"))) {
|
return err_json(EL_STR("missing query"));
|
||||||
q = query_param(path, EL_STR("q"));
|
|
||||||
depth = query_int(path, EL_STR("depth"), 3);
|
|
||||||
} else {
|
|
||||||
q = json_get_string(body, EL_STR("query"));
|
|
||||||
el_val_t bd = json_get_int(body, EL_STR("depth"));
|
|
||||||
if (bd > 0) {
|
|
||||||
depth = bd;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
el_val_t d_raw = ({ el_val_t _if_result_9 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_9 = (query_int(path, EL_STR("depth"), 3)); } else { _if_result_9 = (json_get_int(body, EL_STR("depth"))); } _if_result_9; });
|
||||||
|
el_val_t depth = ({ el_val_t _if_result_10 = 0; if ((d_raw > 0)) { _if_result_10 = (d_raw); } else { _if_result_10 = (3); } _if_result_10; });
|
||||||
return el_str_concat(el_str_concat(EL_STR("{\"results\":"), engram_activate_json(q, depth)), EL_STR("}"));
|
return el_str_concat(el_str_concat(EL_STR("{\"results\":"), engram_activate_json(q, depth)), EL_STR("}"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -202,15 +197,12 @@ el_val_t route_activate(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_create_edge(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t from_id = json_get_string(body, EL_STR("from_id"));
|
el_val_t from_id = json_get_string(body, EL_STR("from_id"));
|
||||||
el_val_t to_id = json_get_string(body, EL_STR("to_id"));
|
el_val_t to_id = json_get_string(body, EL_STR("to_id"));
|
||||||
el_val_t relation = json_get_string(body, EL_STR("relation"));
|
el_val_t rel_raw = json_get_string(body, EL_STR("relation"));
|
||||||
if (str_eq(relation, EL_STR(""))) {
|
el_val_t relation = ({ el_val_t _if_result_11 = 0; if (str_eq(rel_raw, EL_STR(""))) { _if_result_11 = (EL_STR("associates")); } else { _if_result_11 = (rel_raw); } _if_result_11; });
|
||||||
relation = EL_STR("associates");
|
el_val_t w_raw = json_get_float(body, EL_STR("weight"));
|
||||||
}
|
el_val_t weight = ({ el_val_t _if_result_12 = 0; if ((w_raw == el_from_float(0.0))) { _if_result_12 = (el_from_float(0.5)); } else { _if_result_12 = (w_raw); } _if_result_12; });
|
||||||
el_val_t weight = json_get_float(body, EL_STR("weight"));
|
|
||||||
if (weight == el_from_float(0.0)) {
|
|
||||||
weight = el_from_float(0.5);
|
|
||||||
}
|
|
||||||
engram_connect(from_id, to_id, weight, relation);
|
engram_connect(from_id, to_id, weight, relation);
|
||||||
|
el_val_t saved = persist_canonical();
|
||||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"from_id\":\""), from_id), EL_STR("\",\"to_id\":\"")), to_id), EL_STR("\",\"relation\":\"")), relation), EL_STR("\"}"));
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"from_id\":\""), from_id), EL_STR("\",\"to_id\":\"")), to_id), EL_STR("\",\"relation\":\"")), relation), EL_STR("\"}"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -231,6 +223,7 @@ el_val_t route_strengthen(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
return err_json(EL_STR("missing node_id"));
|
return err_json(EL_STR("missing node_id"));
|
||||||
}
|
}
|
||||||
engram_strengthen(id);
|
engram_strengthen(id);
|
||||||
|
el_val_t saved = persist_canonical();
|
||||||
return ok_json();
|
return ok_json();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -241,29 +234,40 @@ el_val_t route_forget(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
return err_json(EL_STR("missing id"));
|
return err_json(EL_STR("missing id"));
|
||||||
}
|
}
|
||||||
engram_forget(id);
|
engram_forget(id);
|
||||||
|
el_val_t saved = persist_canonical();
|
||||||
return ok_json();
|
return ok_json();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_create_ise(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_save(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t content = json_get_string(body, EL_STR("content"));
|
el_val_t p_raw = json_get_string(body, EL_STR("path"));
|
||||||
if (str_eq(content, EL_STR(""))) {
|
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
return err_json(EL_STR("missing content"));
|
el_val_t dir = ({ el_val_t _if_result_13 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_13 = (EL_STR("/tmp/engram")); } else { _if_result_13 = (dir_raw); } _if_result_13; });
|
||||||
}
|
el_val_t p = ({ el_val_t _if_result_14 = 0; if (str_eq(p_raw, EL_STR(""))) { _if_result_14 = (el_str_concat(dir, EL_STR("/snapshot.json"))); } else { _if_result_14 = (p_raw); } _if_result_14; });
|
||||||
el_val_t sal = el_from_float(0.3);
|
engram_save(p);
|
||||||
el_val_t imp = el_from_float(0.3);
|
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"path\":\""), p), EL_STR("\"}"));
|
||||||
el_val_t conf = el_from_float(0.8);
|
return 0;
|
||||||
el_val_t id = engram_node_full(content, EL_STR("InternalStateEvent"), EL_STR("state-event"), sal, imp, conf, EL_STR("Episodic"), EL_STR("[\"internal-state\",\"InternalStateEvent\"]"));
|
}
|
||||||
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\"}"));
|
|
||||||
|
el_val_t route_load(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
|
el_val_t p_raw = json_get_string(body, EL_STR("path"));
|
||||||
|
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
|
el_val_t dir = ({ el_val_t _if_result_15 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_15 = (EL_STR("/tmp/engram")); } else { _if_result_15 = (dir_raw); } _if_result_15; });
|
||||||
|
el_val_t p = ({ el_val_t _if_result_16 = 0; if (str_eq(p_raw, EL_STR(""))) { _if_result_16 = (el_str_concat(dir, EL_STR("/snapshot.json"))); } else { _if_result_16 = (p_raw); } _if_result_16; });
|
||||||
|
engram_load(p);
|
||||||
|
return ok_json();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
el_val_t route_health(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
|
return EL_STR("{\"status\":\"ok\",\"engine\":\"engram-runtime-native\"}");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR"));
|
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
if (str_eq(dir, EL_STR(""))) {
|
el_val_t dir = ({ el_val_t _if_result_17 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_17 = (EL_STR("/tmp/engram")); } else { _if_result_17 = (dir_raw); } _if_result_17; });
|
||||||
dir = EL_STR("/tmp/engram");
|
el_val_t snap_path = el_str_concat(dir, EL_STR("/.sync-export.json"));
|
||||||
}
|
|
||||||
el_val_t snap_path = el_str_concat(dir, EL_STR("/sync-export.json"));
|
|
||||||
engram_save(snap_path);
|
engram_save(snap_path);
|
||||||
el_val_t snap = fs_read(snap_path);
|
el_val_t snap = fs_read(snap_path);
|
||||||
if (str_eq(snap, EL_STR(""))) {
|
if (str_eq(snap, EL_STR(""))) {
|
||||||
@@ -273,36 +277,68 @@ el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_save(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_load_merge(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t p = json_get_string(body, EL_STR("path"));
|
el_val_t p = json_get_string(body, EL_STR("path"));
|
||||||
if (str_eq(p, EL_STR(""))) {
|
if (str_eq(p, EL_STR(""))) {
|
||||||
el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR"));
|
return err_json(EL_STR("path is required"));
|
||||||
if (str_eq(dir, EL_STR(""))) {
|
|
||||||
dir = EL_STR("/tmp/engram");
|
|
||||||
}
|
|
||||||
p = el_str_concat(dir, EL_STR("/snapshot.json"));
|
|
||||||
}
|
}
|
||||||
engram_save(p);
|
if (str_eq(fs_read(p), EL_STR(""))) {
|
||||||
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"path\":\""), p), EL_STR("\"}"));
|
return err_json(EL_STR("file missing or empty"));
|
||||||
|
}
|
||||||
|
el_val_t before_n = engram_node_count();
|
||||||
|
el_val_t before_e = engram_edge_count();
|
||||||
|
engram_load_merge(p);
|
||||||
|
el_val_t added_n = (engram_node_count() - before_n);
|
||||||
|
el_val_t added_e = (engram_edge_count() - before_e);
|
||||||
|
el_val_t saved = persist_canonical();
|
||||||
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"nodes_added\":"), int_to_str(added_n)), EL_STR(",\"edges_added\":")), int_to_str(added_e)), EL_STR(",\"node_count\":")), int_to_str(engram_node_count())), EL_STR("}"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_load(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_emit_ise(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
el_val_t p = json_get_string(body, EL_STR("path"));
|
el_val_t content = json_get_string(body, EL_STR("content"));
|
||||||
if (str_eq(p, EL_STR(""))) {
|
if (str_eq(content, EL_STR(""))) {
|
||||||
el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR"));
|
return err_json(EL_STR("missing content"));
|
||||||
if (str_eq(dir, EL_STR(""))) {
|
|
||||||
dir = EL_STR("/tmp/engram");
|
|
||||||
}
|
|
||||||
p = el_str_concat(dir, EL_STR("/snapshot.json"));
|
|
||||||
}
|
}
|
||||||
engram_load(p);
|
el_val_t sal = el_from_float(0.3);
|
||||||
return ok_json();
|
el_val_t imp = el_from_float(0.3);
|
||||||
|
el_val_t conf = el_from_float(0.8);
|
||||||
|
el_val_t id = engram_node_full(content, EL_STR("InternalStateEvent"), EL_STR("state-event"), sal, imp, conf, EL_STR("Episodic"), EL_STR("[\"internal-state\",\"InternalStateEvent\"]"));
|
||||||
|
el_val_t ret_raw = env(EL_STR("ENGRAM_ISE_RETENTION_MS"));
|
||||||
|
el_val_t ret_ms = ({ el_val_t _if_result_18 = 0; if (str_eq(ret_raw, EL_STR(""))) { _if_result_18 = (172800000); } else { _if_result_18 = (str_to_int(ret_raw)); } _if_result_18; });
|
||||||
|
el_val_t pruned = engram_prune_telemetry(ret_ms);
|
||||||
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\",\"pruned\":")), int_to_str(pruned)), EL_STR("}"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
el_val_t route_health(el_val_t method, el_val_t path, el_val_t body) {
|
el_val_t route_capture_knowledge(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
return EL_STR("{\"status\":\"ok\",\"engine\":\"engram-runtime-native\"}");
|
el_val_t content = json_get_string(body, EL_STR("content"));
|
||||||
|
if (str_eq(content, EL_STR(""))) {
|
||||||
|
return err_json(EL_STR("missing content"));
|
||||||
|
}
|
||||||
|
el_val_t title = json_get_string(body, EL_STR("title"));
|
||||||
|
el_val_t label = ({ el_val_t _if_result_19 = 0; if (str_eq(title, EL_STR(""))) { _if_result_19 = (str_slice(content, 0, 60)); } else { _if_result_19 = (title); } _if_result_19; });
|
||||||
|
el_val_t category_raw = json_get_string(body, EL_STR("category"));
|
||||||
|
el_val_t category = ({ el_val_t _if_result_20 = 0; if (str_eq(category_raw, EL_STR(""))) { _if_result_20 = (EL_STR("other")); } else { _if_result_20 = (category_raw); } _if_result_20; });
|
||||||
|
el_val_t ktier_raw = json_get_string(body, EL_STR("tier"));
|
||||||
|
el_val_t ktier = ({ el_val_t _if_result_21 = 0; if (str_eq(ktier_raw, EL_STR(""))) { _if_result_21 = (EL_STR("note")); } else { _if_result_21 = (ktier_raw); } _if_result_21; });
|
||||||
|
el_val_t project = json_get_string(body, EL_STR("project"));
|
||||||
|
el_val_t tags_raw = json_get_raw(body, EL_STR("tags"));
|
||||||
|
el_val_t tags_base = ({ el_val_t _if_result_22 = 0; if (str_eq(tags_raw, EL_STR(""))) { _if_result_22 = (EL_STR("[]")); } else { _if_result_22 = (tags_raw); } _if_result_22; });
|
||||||
|
el_val_t base_len = str_len(tags_base);
|
||||||
|
el_val_t head = str_slice(tags_base, 0, (base_len - 1));
|
||||||
|
el_val_t sep = ({ el_val_t _if_result_23 = 0; if (str_eq(head, EL_STR("["))) { _if_result_23 = (EL_STR("")); } else { _if_result_23 = (EL_STR(",")); } _if_result_23; });
|
||||||
|
el_val_t safe_cat = str_replace(category, EL_STR("\""), EL_STR("'"));
|
||||||
|
el_val_t safe_tier = str_replace(ktier, EL_STR("\""), EL_STR("'"));
|
||||||
|
el_val_t safe_proj = str_replace(project, EL_STR("\""), EL_STR("'"));
|
||||||
|
el_val_t proj_tag = ({ el_val_t _if_result_24 = 0; if (str_eq(safe_proj, EL_STR(""))) { _if_result_24 = (EL_STR("")); } else { _if_result_24 = (el_str_concat(el_str_concat(EL_STR(",\"project:"), safe_proj), EL_STR("\""))); } _if_result_24; });
|
||||||
|
el_val_t tags = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(head, sep), EL_STR("\"category:")), safe_cat), EL_STR("\",\"tier:")), safe_tier), EL_STR("\"")), proj_tag), EL_STR("]"));
|
||||||
|
el_val_t sal = el_from_float(0.5);
|
||||||
|
el_val_t imp = el_from_float(0.5);
|
||||||
|
el_val_t conf = el_from_float(0.9);
|
||||||
|
el_val_t id = engram_node_full(content, EL_STR("Knowledge"), label, sal, imp, conf, EL_STR("Semantic"), tags);
|
||||||
|
el_val_t saved = persist_canonical();
|
||||||
|
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\"}"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,12 +365,15 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
return route_health(method, path, body);
|
return route_health(method, path, body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (str_eq(method, EL_STR("POST")) && str_starts_with(clean, EL_STR("/api/neuron/state-events"))) {
|
if (str_eq(method, EL_STR("POST")) && str_eq(clean, EL_STR("/api/neuron/state-events"))) {
|
||||||
return route_create_ise(method, path, body);
|
return route_emit_ise(method, path, body);
|
||||||
}
|
}
|
||||||
if (!check_auth_ok(method, body)) {
|
if (!check_auth_ok(method, body)) {
|
||||||
return err_json(EL_STR("unauthorized"));
|
return err_json(EL_STR("unauthorized"));
|
||||||
}
|
}
|
||||||
|
if (str_eq(method, EL_STR("POST")) && str_eq(clean, EL_STR("/api/neuron/knowledge/capture"))) {
|
||||||
|
return route_capture_knowledge(method, path, body);
|
||||||
|
}
|
||||||
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/stats")) || str_eq(clean, EL_STR("/stats")))) {
|
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/stats")) || str_eq(clean, EL_STR("/stats")))) {
|
||||||
return route_stats(method, path, body);
|
return route_stats(method, path, body);
|
||||||
}
|
}
|
||||||
@@ -374,32 +413,40 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/strengthen")) || str_eq(clean, EL_STR("/strengthen")))) {
|
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/strengthen")) || str_eq(clean, EL_STR("/strengthen")))) {
|
||||||
return route_strengthen(method, path, body);
|
return route_strengthen(method, path, body);
|
||||||
}
|
}
|
||||||
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/sync")) || str_eq(clean, EL_STR("/sync")))) {
|
|
||||||
return route_sync(method, path, body);
|
|
||||||
}
|
|
||||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/save")) || str_eq(clean, EL_STR("/save")))) {
|
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/save")) || str_eq(clean, EL_STR("/save")))) {
|
||||||
return route_save(method, path, body);
|
return route_save(method, path, body);
|
||||||
}
|
}
|
||||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/load")) || str_eq(clean, EL_STR("/load")))) {
|
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/load")) || str_eq(clean, EL_STR("/load")))) {
|
||||||
return route_load(method, path, body);
|
return route_load(method, path, body);
|
||||||
}
|
}
|
||||||
|
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/load-merge")) || str_eq(clean, EL_STR("/load-merge")))) {
|
||||||
|
return route_load_merge(method, path, body);
|
||||||
|
}
|
||||||
|
if (str_eq(method, EL_STR("GET")) && str_eq(clean, EL_STR("/api/sync"))) {
|
||||||
|
return route_sync(method, path, body);
|
||||||
|
}
|
||||||
return el_str_concat(el_str_concat(EL_STR("{\"error\":\"not found\",\"path\":\""), clean), EL_STR("\"}"));
|
return el_str_concat(el_str_concat(EL_STR("{\"error\":\"not found\",\"path\":\""), clean), EL_STR("\"}"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int _argc, char** _argv) {
|
int main(int _argc, char** _argv) {
|
||||||
el_runtime_init_args(_argc, _argv);
|
el_runtime_init_args(_argc, _argv);
|
||||||
bind_str = env(EL_STR("ENGRAM_BIND"));
|
bind_raw = env(EL_STR("ENGRAM_BIND"));
|
||||||
if (str_eq(bind_str, EL_STR(""))) {
|
bind_str = ({ el_val_t _if_result_25 = 0; if (str_eq(bind_raw, EL_STR(""))) { _if_result_25 = (EL_STR(":8742")); } else { _if_result_25 = (bind_raw); } _if_result_25; });
|
||||||
bind_str = EL_STR(":8742");
|
|
||||||
}
|
|
||||||
port = parse_port(bind_str);
|
port = parse_port(bind_str);
|
||||||
data_dir = env(EL_STR("ENGRAM_DATA_DIR"));
|
data_dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
if (str_eq(data_dir, EL_STR(""))) {
|
data_dir = ({ el_val_t _if_result_26 = 0; if (str_eq(data_dir_raw, EL_STR(""))) { _if_result_26 = (EL_STR("/tmp/engram")); } else { _if_result_26 = (data_dir_raw); } _if_result_26; });
|
||||||
data_dir = EL_STR("/tmp/engram");
|
|
||||||
}
|
|
||||||
snapshot_path = el_str_concat(data_dir, EL_STR("/snapshot.json"));
|
snapshot_path = el_str_concat(data_dir, EL_STR("/snapshot.json"));
|
||||||
engram_load(snapshot_path);
|
engram_load(snapshot_path);
|
||||||
|
boot_snap = fs_read(snapshot_path);
|
||||||
|
if (!str_eq(boot_snap, EL_STR(""))) {
|
||||||
|
if (engram_node_count() == 0) {
|
||||||
|
println(EL_STR("[engram] WARNING: snapshot.json is non-empty but load produced 0 nodes \xe2\x80\x94 preserving copy at snapshot.failed-load.json"));
|
||||||
|
fs_write(el_str_concat(data_dir, EL_STR("/snapshot.failed-load.json")), boot_snap);
|
||||||
|
} else {
|
||||||
|
fs_write(el_str_concat(data_dir, EL_STR("/snapshot.boot-backup.json")), boot_snap);
|
||||||
|
}
|
||||||
|
}
|
||||||
println(EL_STR("[engram] runtime-native graph engine"));
|
println(EL_STR("[engram] runtime-native graph engine"));
|
||||||
println(el_str_concat(EL_STR("[engram] data_dir="), data_dir));
|
println(el_str_concat(EL_STR("[engram] data_dir="), data_dir));
|
||||||
println(el_str_concat(EL_STR("[engram] node_count="), int_to_str(engram_node_count())));
|
println(el_str_concat(EL_STR("[engram] node_count="), int_to_str(engram_node_count())));
|
||||||
|
|||||||
+190
-54
@@ -76,13 +76,43 @@ fn route_stats(method: String, path: String, body: String) -> String {
|
|||||||
engram_stats_json()
|
engram_stats_json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (2026-07-18 self-review) Scoping sweep: `let` inside an if-block creates an
|
||||||
|
// inner scope only — it does NOT mutate the outer binding (documented with
|
||||||
|
// evidence in awareness.el, 2026-05-25). Every default/reassignment below used
|
||||||
|
// that broken pattern, so defaults never applied: nodes were created with
|
||||||
|
// node_type="" and salience=0.0, /api/search and /api/activate ALWAYS ran with
|
||||||
|
// q="" regardless of input, edges defaulted to relation=""/weight=0.0, and
|
||||||
|
// save/load with no "path" hit engram_save(""). Rewritten to the
|
||||||
|
// `let x = if cond { a } else { b }` expression form (the pattern the newer
|
||||||
|
// routes route_emit_ise/route_capture_knowledge already use correctly).
|
||||||
|
// persist_canonical — save the canonical snapshot after a durable write.
|
||||||
|
//
|
||||||
|
// WHY (2026-07-22 self-review): the 2026-07-21 fix correctly stopped READ
|
||||||
|
// routes from writing the canonical snapshot.json — but nothing was left
|
||||||
|
// that saved it on WRITE. Every mutation (node create, edge create,
|
||||||
|
// knowledge capture, forget, merge) lived only in RAM until someone POSTed
|
||||||
|
// /api/save manually; a process restart silently discarded everything since
|
||||||
|
// the last manual save. Observed live: two engram restarts during the
|
||||||
|
// 2026-07-22 review reverted the store to a ~17h-old snapshot, destroying
|
||||||
|
// same-day writes. Reads must never write the canonical; writes must always
|
||||||
|
// persist it. ISE telemetry is deliberately excluded (48h-pruned, loss-
|
||||||
|
// tolerant, ~2/min — snapshotting the whole store per heartbeat is waste;
|
||||||
|
// any durable write that follows persists the pruning too).
|
||||||
|
fn persist_canonical() -> Int {
|
||||||
|
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||||
|
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||||
|
engram_save(dir + "/snapshot.json")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
fn route_create_node(method: String, path: String, body: String) -> String {
|
fn route_create_node(method: String, path: String, body: String) -> String {
|
||||||
let content: String = json_get_string(body, "content")
|
let content: String = json_get_string(body, "content")
|
||||||
let node_type: String = json_get_string(body, "node_type")
|
let nt_raw: String = json_get_string(body, "node_type")
|
||||||
if str_eq(node_type, "") { let node_type = "Memory" }
|
let node_type: String = if str_eq(nt_raw, "") { "Memory" } else { nt_raw }
|
||||||
let salience: Float = json_get_float(body, "salience")
|
let sal_raw: Float = json_get_float(body, "salience")
|
||||||
if salience == 0.0 { let salience = 0.5 }
|
let salience: Float = if sal_raw == 0.0 { 0.5 } else { sal_raw }
|
||||||
let id: String = engram_node(content, node_type, salience)
|
let id: String = engram_node(content, node_type, salience)
|
||||||
|
let saved: Int = persist_canonical()
|
||||||
"{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\"}"
|
"{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,13 +133,14 @@ fn route_scan_nodes(method: String, path: String, body: String) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// route_scan_edges — bulk export of all edges as a JSON array. Implemented
|
// route_scan_edges — bulk export of all edges as a JSON array. Implemented
|
||||||
// via engram_save → fs_read of the canonical on-disk snapshot, which the
|
// via engram_save → fs_read of a SCRATCH export path. (2026-07-21 self-review:
|
||||||
// runtime keeps in lockstep with the in-memory graph. Live against the
|
// previously this saved over the canonical snapshot.json on every GET — if the
|
||||||
// running graph, not a stale export.
|
// process ever booted with a partial/empty store, the first read request
|
||||||
|
// clobbered the good snapshot. Read routes must never write the canonical path.)
|
||||||
fn route_scan_edges(method: String, path: String, body: String) -> String {
|
fn route_scan_edges(method: String, path: String, body: String) -> String {
|
||||||
let dir: String = env("ENGRAM_DATA_DIR")
|
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||||
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||||
let snap_path: String = dir + "/snapshot.json"
|
let snap_path: String = dir + "/.scan-export.json"
|
||||||
engram_save(snap_path)
|
engram_save(snap_path)
|
||||||
let snap: String = fs_read(snap_path)
|
let snap: String = fs_read(snap_path)
|
||||||
if str_eq(snap, "") { return "[]" }
|
if str_eq(snap, "") { return "[]" }
|
||||||
@@ -122,40 +153,34 @@ fn route_scan_edges(method: String, path: String, body: String) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn route_search(method: String, path: String, body: String) -> String {
|
fn route_search(method: String, path: String, body: String) -> String {
|
||||||
let q: String = ""
|
let q: String = if str_eq(method, "GET") { query_param(path, "q") } else { json_get_string(body, "query") }
|
||||||
if str_eq(method, "GET") {
|
let lim_url: Int = query_int(path, "limit", 0)
|
||||||
let q = query_param(path, "q")
|
let lim_body: Int = json_get_int(body, "limit")
|
||||||
} else {
|
let lim_either: Int = if lim_url > 0 { lim_url } else { lim_body }
|
||||||
let q = json_get_string(body, "query")
|
let limit: Int = if lim_either > 0 { lim_either } else { 20 }
|
||||||
}
|
|
||||||
let limit: Int = query_int(path, "limit", 20)
|
|
||||||
if limit == 0 { let limit = json_get_int(body, "limit") }
|
|
||||||
if limit == 0 { let limit = 20 }
|
|
||||||
return engram_search_json(q, limit)
|
return engram_search_json(q, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route_activate(method: String, path: String, body: String) -> String {
|
fn route_activate(method: String, path: String, body: String) -> String {
|
||||||
let q: String = ""
|
let q: String = if str_eq(method, "GET") { query_param(path, "q") } else { json_get_string(body, "query") }
|
||||||
let depth: Int = 3
|
// Guard: engram_activate with an empty query matches zero seeds, which
|
||||||
if str_eq(method, "GET") {
|
// zeroes ALL carried working-memory weights (documented in awareness.el
|
||||||
let q = query_param(path, "q")
|
// perceive()). Never let an empty activation through to wipe WM.
|
||||||
let depth = query_int(path, "depth", 3)
|
if str_eq(q, "") { return err_json("missing query") }
|
||||||
} else {
|
let d_raw: Int = if str_eq(method, "GET") { query_int(path, "depth", 3) } else { json_get_int(body, "depth") }
|
||||||
let q = json_get_string(body, "query")
|
let depth: Int = if d_raw > 0 { d_raw } else { 3 }
|
||||||
let bd: Int = json_get_int(body, "depth")
|
|
||||||
if bd > 0 { let depth = bd }
|
|
||||||
}
|
|
||||||
return "{\"results\":" + engram_activate_json(q, depth) + "}"
|
return "{\"results\":" + engram_activate_json(q, depth) + "}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route_create_edge(method: String, path: String, body: String) -> String {
|
fn route_create_edge(method: String, path: String, body: String) -> String {
|
||||||
let from_id: String = json_get_string(body, "from_id")
|
let from_id: String = json_get_string(body, "from_id")
|
||||||
let to_id: String = json_get_string(body, "to_id")
|
let to_id: String = json_get_string(body, "to_id")
|
||||||
let relation: String = json_get_string(body, "relation")
|
let rel_raw: String = json_get_string(body, "relation")
|
||||||
if str_eq(relation, "") { let relation = "associates" }
|
let relation: String = if str_eq(rel_raw, "") { "associates" } else { rel_raw }
|
||||||
let weight: Float = json_get_float(body, "weight")
|
let w_raw: Float = json_get_float(body, "weight")
|
||||||
if weight == 0.0 { let weight = 0.5 }
|
let weight: Float = if w_raw == 0.0 { 0.5 } else { w_raw }
|
||||||
engram_connect(from_id, to_id, weight, relation)
|
engram_connect(from_id, to_id, weight, relation)
|
||||||
|
let saved: Int = persist_canonical()
|
||||||
"{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + relation + "\"}"
|
"{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + relation + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +195,7 @@ fn route_strengthen(method: String, path: String, body: String) -> String {
|
|||||||
let id: String = json_get_string(body, "node_id")
|
let id: String = json_get_string(body, "node_id")
|
||||||
if str_eq(id, "") { return err_json("missing node_id") }
|
if str_eq(id, "") { return err_json("missing node_id") }
|
||||||
engram_strengthen(id)
|
engram_strengthen(id)
|
||||||
|
let saved: Int = persist_canonical()
|
||||||
ok_json()
|
ok_json()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,27 +203,24 @@ fn route_forget(method: String, path: String, body: String) -> String {
|
|||||||
let id: String = extract_id(path, "/api/nodes/")
|
let id: String = extract_id(path, "/api/nodes/")
|
||||||
if str_eq(id, "") { return err_json("missing id") }
|
if str_eq(id, "") { return err_json("missing id") }
|
||||||
engram_forget(id)
|
engram_forget(id)
|
||||||
|
let saved: Int = persist_canonical()
|
||||||
ok_json()
|
ok_json()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route_save(method: String, path: String, body: String) -> String {
|
fn route_save(method: String, path: String, body: String) -> String {
|
||||||
let p: String = json_get_string(body, "path")
|
let p_raw: String = json_get_string(body, "path")
|
||||||
if str_eq(p, "") {
|
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||||
let dir: String = env("ENGRAM_DATA_DIR")
|
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||||
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
let p: String = if str_eq(p_raw, "") { dir + "/snapshot.json" } else { p_raw }
|
||||||
let p = dir + "/snapshot.json"
|
|
||||||
}
|
|
||||||
engram_save(p)
|
engram_save(p)
|
||||||
"{\"ok\":true,\"path\":\"" + p + "\"}"
|
"{\"ok\":true,\"path\":\"" + p + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route_load(method: String, path: String, body: String) -> String {
|
fn route_load(method: String, path: String, body: String) -> String {
|
||||||
let p: String = json_get_string(body, "path")
|
let p_raw: String = json_get_string(body, "path")
|
||||||
if str_eq(p, "") {
|
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||||
let dir: String = env("ENGRAM_DATA_DIR")
|
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||||
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
let p: String = if str_eq(p_raw, "") { dir + "/snapshot.json" } else { p_raw }
|
||||||
let p = dir + "/snapshot.json"
|
|
||||||
}
|
|
||||||
engram_load(p)
|
engram_load(p)
|
||||||
ok_json()
|
ok_json()
|
||||||
}
|
}
|
||||||
@@ -219,15 +242,36 @@ fn route_health(method: String, path: String, body: String) -> String {
|
|||||||
// (it skips nodes already present by ID). Auth-exempt: same-host internal call.
|
// (it skips nodes already present by ID). Auth-exempt: same-host internal call.
|
||||||
// (2026-06-27 self-review: added this route to fix silent 10-min sync failures)
|
// (2026-06-27 self-review: added this route to fix silent 10-min sync failures)
|
||||||
fn route_sync(method: String, path: String, body: String) -> String {
|
fn route_sync(method: String, path: String, body: String) -> String {
|
||||||
let dir: String = env("ENGRAM_DATA_DIR")
|
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||||
if str_eq(dir, "") { let dir = "/tmp/engram" }
|
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||||
let snap_path: String = dir + "/snapshot.json"
|
// 2026-07-21 self-review: export to a scratch path, never the canonical
|
||||||
|
// snapshot.json — read routes must not be able to clobber the good snapshot.
|
||||||
|
let snap_path: String = dir + "/.sync-export.json"
|
||||||
engram_save(snap_path)
|
engram_save(snap_path)
|
||||||
let snap: String = fs_read(snap_path)
|
let snap: String = fs_read(snap_path)
|
||||||
if str_eq(snap, "") { return "{\"nodes\":[],\"edges\":[]}" }
|
if str_eq(snap, "") { return "{\"nodes\":[],\"edges\":[]}" }
|
||||||
return snap
|
return snap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// route_load_merge — POST /api/load-merge {"path": "..."} — merge a snapshot
|
||||||
|
// file into the live store WITHOUT resetting it (engram_load_merge skips nodes
|
||||||
|
// already present by id). Added 2026-07-21 self-review to restore the 244 kn-
|
||||||
|
// identity Knowledge nodes lost from the snapshot lineage between 05-13 and
|
||||||
|
// 07-13. Requires an explicit path: refuses to run without one so it can never
|
||||||
|
// be triggered accidentally against a default.
|
||||||
|
fn route_load_merge(method: String, path: String, body: String) -> String {
|
||||||
|
let p: String = json_get_string(body, "path")
|
||||||
|
if str_eq(p, "") { return err_json("path is required") }
|
||||||
|
if str_eq(fs_read(p), "") { return err_json("file missing or empty") }
|
||||||
|
let before_n: Int = engram_node_count()
|
||||||
|
let before_e: Int = engram_edge_count()
|
||||||
|
engram_load_merge(p)
|
||||||
|
let added_n: Int = engram_node_count() - before_n
|
||||||
|
let added_e: Int = engram_edge_count() - before_e
|
||||||
|
let saved: Int = persist_canonical()
|
||||||
|
"{\"ok\":true,\"nodes_added\":" + int_to_str(added_n) + ",\"edges_added\":" + int_to_str(added_e) + ",\"node_count\":" + int_to_str(engram_node_count()) + "}"
|
||||||
|
}
|
||||||
|
|
||||||
// route_emit_ise — write an InternalStateEvent node from the soul daemon.
|
// route_emit_ise — write an InternalStateEvent node from the soul daemon.
|
||||||
//
|
//
|
||||||
// Endpoint: POST /api/neuron/state-events
|
// Endpoint: POST /api/neuron/state-events
|
||||||
@@ -241,10 +285,20 @@ fn route_sync(method: String, path: String, body: String) -> String {
|
|||||||
//
|
//
|
||||||
// Salience/importance set to match engram_node_full ISE defaults used by the
|
// Salience/importance set to match engram_node_full ISE defaults used by the
|
||||||
// in-process fallback path in awareness.el (salience=0.3, importance=0.3,
|
// in-process fallback path in awareness.el (salience=0.3, importance=0.3,
|
||||||
// confidence=0.8, tier=Episodic). High temporal_decay_rate (1.617) — ISEs
|
// confidence=0.8, tier=Episodic).
|
||||||
// are inherently transient; they should decay faster than structural knowledge.
|
|
||||||
// (2026-06-26 self-review: added this route after discovering ise_post was
|
// (2026-06-26 self-review: added this route after discovering ise_post was
|
||||||
// silently failing — the soul posts here but the endpoint didn't exist.)
|
// silently failing — the soul posts here but the endpoint didn't exist.)
|
||||||
|
//
|
||||||
|
// Retention (2026-07-16 self-review): an earlier comment here claimed ISEs
|
||||||
|
// got temporal_decay_rate=1.617 — that was never implemented (engram_node_full
|
||||||
|
// hardcodes 0.0), and per-node decay only dampens activation anyway; it never
|
||||||
|
// removes nodes. By 2026-07-16 ISEs were 75% of the store (10,175 of 13,522
|
||||||
|
// nodes, ~4,300/day, unbounded). ISEs are already WM-excluded in
|
||||||
|
// engram_activate, so the fix is retention, not decay: every insert calls
|
||||||
|
// engram_prune_telemetry(), a single O(nodes+edges) compaction pass that
|
||||||
|
// removes ISEs older than ENGRAM_ISE_RETENTION_MS (default 48h), protecting
|
||||||
|
// "session-start" labels and self_review events as durable history. At
|
||||||
|
// ~3 ISEs/min this bounds telemetry at ~8.6k nodes instead of growing forever.
|
||||||
fn route_emit_ise(method: String, path: String, body: String) -> String {
|
fn route_emit_ise(method: String, path: String, body: String) -> String {
|
||||||
let content: String = json_get_string(body, "content")
|
let content: String = json_get_string(body, "content")
|
||||||
if str_eq(content, "") { return err_json("missing content") }
|
if str_eq(content, "") { return err_json("missing content") }
|
||||||
@@ -256,6 +310,64 @@ fn route_emit_ise(method: String, path: String, body: String) -> String {
|
|||||||
sal, imp, conf,
|
sal, imp, conf,
|
||||||
"Episodic", "[\"internal-state\",\"InternalStateEvent\"]"
|
"Episodic", "[\"internal-state\",\"InternalStateEvent\"]"
|
||||||
)
|
)
|
||||||
|
let ret_raw: String = env("ENGRAM_ISE_RETENTION_MS")
|
||||||
|
let ret_ms: Int = if str_eq(ret_raw, "") { 172800000 } else { str_to_int(ret_raw) }
|
||||||
|
let pruned: Int = engram_prune_telemetry(ret_ms)
|
||||||
|
"{\"ok\":true,\"id\":\"" + id + "\",\"pruned\":" + int_to_str(pruned) + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Knowledge capture ─────────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// route_capture_knowledge — direct Knowledge-node capture over HTTP.
|
||||||
|
//
|
||||||
|
// Endpoint: POST /api/neuron/knowledge/capture (auth required: "_auth" in body)
|
||||||
|
// Body: {"content": "...", "title": "...", "category": "...",
|
||||||
|
// "tier": "note|lesson|canonical", "tags": [...], "project": "...",
|
||||||
|
// "_auth": "<key>"}
|
||||||
|
//
|
||||||
|
// WHY (2026-07-15 self-review): the world-ingestor integrator was designed
|
||||||
|
// against this endpoint (its MCP-unavailable fallback), but the route never
|
||||||
|
// existed — every direct push 404'd, and because the auth gate ran before
|
||||||
|
// routing, the failure surfaced as {"error":"unauthorized"} and was
|
||||||
|
// misdiagnosed for two weeks while world knowledge silently dropped.
|
||||||
|
// POST /api/nodes was no substitute: it discards label/tags/tier, which
|
||||||
|
// makes captured knowledge invisible to tag-scoped search and curiosity.
|
||||||
|
//
|
||||||
|
// The incoming knowledge tier (note/lesson/canonical) is preserved as a
|
||||||
|
// "tier:<x>" tag rather than mapped onto Engram's cognitive tiers — Knowledge
|
||||||
|
// nodes land in Semantic (stable reference), and the epistemic tier stays
|
||||||
|
// queryable without inventing a lossy mapping.
|
||||||
|
fn route_capture_knowledge(method: String, path: String, body: String) -> String {
|
||||||
|
let content: String = json_get_string(body, "content")
|
||||||
|
if str_eq(content, "") { return err_json("missing content") }
|
||||||
|
let title: String = json_get_string(body, "title")
|
||||||
|
let label: String = if str_eq(title, "") { str_slice(content, 0, 60) } else { title }
|
||||||
|
let category_raw: String = json_get_string(body, "category")
|
||||||
|
let category: String = if str_eq(category_raw, "") { "other" } else { category_raw }
|
||||||
|
let ktier_raw: String = json_get_string(body, "tier")
|
||||||
|
let ktier: String = if str_eq(ktier_raw, "") { "note" } else { ktier_raw }
|
||||||
|
let project: String = json_get_string(body, "project")
|
||||||
|
let tags_raw: String = json_get_raw(body, "tags")
|
||||||
|
let tags_base: String = if str_eq(tags_raw, "") { "[]" } else { tags_raw }
|
||||||
|
// Merge category/tier/project markers into the tag array. Search matches
|
||||||
|
// against the tags string, so these make captures findable by facet.
|
||||||
|
let base_len: Int = str_len(tags_base)
|
||||||
|
let head: String = str_slice(tags_base, 0, base_len - 1)
|
||||||
|
let sep: String = if str_eq(head, "[") { "" } else { "," }
|
||||||
|
let safe_cat: String = str_replace(category, "\"", "'")
|
||||||
|
let safe_tier: String = str_replace(ktier, "\"", "'")
|
||||||
|
let safe_proj: String = str_replace(project, "\"", "'")
|
||||||
|
let proj_tag: String = if str_eq(safe_proj, "") { "" } else { ",\"project:" + safe_proj + "\"" }
|
||||||
|
let tags: String = head + sep + "\"category:" + safe_cat + "\",\"tier:" + safe_tier + "\"" + proj_tag + "]"
|
||||||
|
let sal: Float = 0.5
|
||||||
|
let imp: Float = 0.5
|
||||||
|
let conf: Float = 0.9
|
||||||
|
let id: String = engram_node_full(
|
||||||
|
content, "Knowledge", label,
|
||||||
|
sal, imp, conf,
|
||||||
|
"Semantic", tags
|
||||||
|
)
|
||||||
|
let saved: Int = persist_canonical()
|
||||||
"{\"ok\":true,\"id\":\"" + id + "\"}"
|
"{\"ok\":true,\"id\":\"" + id + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,6 +407,12 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
|||||||
return err_json("unauthorized")
|
return err_json("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Knowledge capture (auth enforced above; the world-ingestor integrator
|
||||||
|
// and any headless session without MCP push knowledge through this)
|
||||||
|
if str_eq(method, "POST") && str_eq(clean, "/api/neuron/knowledge/capture") {
|
||||||
|
return route_capture_knowledge(method, path, body)
|
||||||
|
}
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
if str_eq(method, "GET") && (str_eq(clean, "/api/stats") || str_eq(clean, "/stats")) {
|
if str_eq(method, "GET") && (str_eq(clean, "/api/stats") || str_eq(clean, "/stats")) {
|
||||||
return route_stats(method, path, body)
|
return route_stats(method, path, body)
|
||||||
@@ -351,6 +469,9 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
|||||||
if str_eq(method, "POST") && (str_eq(clean, "/api/load") || str_eq(clean, "/load")) {
|
if str_eq(method, "POST") && (str_eq(clean, "/api/load") || str_eq(clean, "/load")) {
|
||||||
return route_load(method, path, body)
|
return route_load(method, path, body)
|
||||||
}
|
}
|
||||||
|
if str_eq(method, "POST") && (str_eq(clean, "/api/load-merge") || str_eq(clean, "/load-merge")) {
|
||||||
|
return route_load_merge(method, path, body)
|
||||||
|
}
|
||||||
|
|
||||||
// Sync — soul daemon periodic pull of non-ISE knowledge into in-process graph
|
// Sync — soul daemon periodic pull of non-ISE knowledge into in-process graph
|
||||||
if str_eq(method, "GET") && str_eq(clean, "/api/sync") {
|
if str_eq(method, "GET") && str_eq(clean, "/api/sync") {
|
||||||
@@ -362,16 +483,31 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
|||||||
|
|
||||||
// ── Entry ─────────────────────────────────────────────────────────────────────
|
// ── Entry ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let bind_str: String = env("ENGRAM_BIND")
|
let bind_raw: String = env("ENGRAM_BIND")
|
||||||
if str_eq(bind_str, "") { let bind_str = ":8742" }
|
let bind_str: String = if str_eq(bind_raw, "") { ":8742" } else { bind_raw }
|
||||||
let port: Int = parse_port(bind_str)
|
let port: Int = parse_port(bind_str)
|
||||||
|
|
||||||
// On startup, try to load any existing snapshot (best effort).
|
// On startup, try to load any existing snapshot (best effort).
|
||||||
let data_dir: String = env("ENGRAM_DATA_DIR")
|
let data_dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||||
if str_eq(data_dir, "") { let data_dir = "/tmp/engram" }
|
let data_dir: String = if str_eq(data_dir_raw, "") { "/tmp/engram" } else { data_dir_raw }
|
||||||
let snapshot_path: String = data_dir + "/snapshot.json"
|
let snapshot_path: String = data_dir + "/snapshot.json"
|
||||||
engram_load(snapshot_path)
|
engram_load(snapshot_path)
|
||||||
|
|
||||||
|
// 2026-07-21 self-review boot guard: if the snapshot file has content but the
|
||||||
|
// load produced 0 nodes, something is wrong (corrupt file / parse failure).
|
||||||
|
// Preserve the evidence and warn loudly — and since read routes no longer write
|
||||||
|
// the canonical path, a bad boot can no longer clobber the good snapshot.
|
||||||
|
let boot_snap: String = fs_read(snapshot_path)
|
||||||
|
if !str_eq(boot_snap, "") {
|
||||||
|
if engram_node_count() == 0 {
|
||||||
|
println("[engram] WARNING: snapshot.json is non-empty but load produced 0 nodes — preserving copy at snapshot.failed-load.json")
|
||||||
|
fs_write(data_dir + "/snapshot.failed-load.json", boot_snap)
|
||||||
|
} else {
|
||||||
|
// Good load: keep a boot-time backup of the snapshot as loaded.
|
||||||
|
fs_write(data_dir + "/snapshot.boot-backup.json", boot_snap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
println("[engram] runtime-native graph engine")
|
println("[engram] runtime-native graph engine")
|
||||||
println("[engram] data_dir=" + data_dir)
|
println("[engram] data_dir=" + data_dir)
|
||||||
println("[engram] node_count=" + int_to_str(engram_node_count()))
|
println("[engram] node_count=" + int_to_str(engram_node_count()))
|
||||||
|
|||||||
@@ -17,6 +17,16 @@
|
|||||||
// 4. Append dep to order after all its transitive deps
|
// 4. Append dep to order after all its transitive deps
|
||||||
// 5. Deduplicate: skip already-ordered vessels
|
// 5. Deduplicate: skip already-ordered vessels
|
||||||
|
|
||||||
|
// ── Cross-module forward declarations ─────────────────────────────────────────
|
||||||
|
// Defined in sibling epm modules; resolved at link time. The `extern fn` decls
|
||||||
|
// give elc the C prototypes so generated install.c compiles cleanly under strict
|
||||||
|
// compilers (gcc>=14 / clang) that reject implicit function declarations.
|
||||||
|
extern fn manifest_name(src: String) -> String // manifest.el
|
||||||
|
extern fn manifest_deps(src: String) -> String // manifest.el
|
||||||
|
extern fn registry_token() -> String // registry.el
|
||||||
|
extern fn registry_find(name: String, version: String) -> String // registry.el
|
||||||
|
extern fn registry_latest_version(name: String) -> String // registry.el
|
||||||
|
|
||||||
// ── Install paths ─────────────────────────────────────────────────────────────
|
// ── Install paths ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// packages_dir returns the root directory for installed vessels.
|
// packages_dir returns the root directory for installed vessels.
|
||||||
|
|||||||
@@ -14,6 +14,15 @@
|
|||||||
// EPM_REGISTRY_ORG — org name that hosts vessel repos (default: neuron-technologies)
|
// EPM_REGISTRY_ORG — org name that hosts vessel repos (default: neuron-technologies)
|
||||||
// EPM_TOKEN — Gitea personal access token (required for publish)
|
// EPM_TOKEN — Gitea personal access token (required for publish)
|
||||||
|
|
||||||
|
// ── Cross-module forward declarations ─────────────────────────────────────────
|
||||||
|
// These symbols are defined in sibling epm modules or the El runtime and are
|
||||||
|
// resolved at link time. The `extern fn` decls give elc the C prototype so the
|
||||||
|
// generated registry.c compiles cleanly under strict compilers (gcc>=14 / clang)
|
||||||
|
// that reject implicit function declarations. Signature arity must match the
|
||||||
|
// definition; return/param types are informational (all lower to el_val_t).
|
||||||
|
extern fn config(key: String) -> String // El runtime builtin
|
||||||
|
extern fn read_installed() -> String // install.el
|
||||||
|
|
||||||
// ── Config helpers ────────────────────────────────────────────────────────────
|
// ── Config helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// registry_api_url returns the Gitea API base URL with no trailing slash.
|
// registry_api_url returns the Gitea API base URL with no trailing slash.
|
||||||
|
|||||||
@@ -6,6 +6,15 @@
|
|||||||
// Depends on: registry.el (registry_latest_version, registry_find),
|
// Depends on: registry.el (registry_latest_version, registry_find),
|
||||||
// install.el (read_installed, install_vessel, installed_version)
|
// install.el (read_installed, install_vessel, installed_version)
|
||||||
|
|
||||||
|
// ── Cross-module forward declarations ─────────────────────────────────────────
|
||||||
|
// Defined in sibling epm modules; resolved at link time. The `extern fn` decls
|
||||||
|
// give elc the C prototypes so generated update.c compiles cleanly under strict
|
||||||
|
// compilers (gcc>=14 / clang) that reject implicit function declarations.
|
||||||
|
extern fn read_installed() -> String // install.el
|
||||||
|
extern fn installed_version(name: String) -> String // install.el
|
||||||
|
extern fn install_vessel(name: String, version: String) -> Bool // install.el
|
||||||
|
extern fn registry_latest_version(name: String) -> String // registry.el
|
||||||
|
|
||||||
// ── Semver helpers ────────────────────────────────────────────────────────────
|
// ── Semver helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// semver_part extracts the Nth dot-separated component from a semver string.
|
// semver_part extracts the Nth dot-separated component from a semver string.
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ static inline void* el_win_dlsym(void* handle, const char* name) {
|
|||||||
#include <direct.h> /* _mkdir */
|
#include <direct.h> /* _mkdir */
|
||||||
#define mkdir(path, mode) _mkdir(path) /* POSIX mkdir(path,mode) → _mkdir(path) */
|
#define mkdir(path, mode) _mkdir(path) /* POSIX mkdir(path,mode) → _mkdir(path) */
|
||||||
#define timegm _mkgmtime /* UTC tm → time_t */
|
#define timegm _mkgmtime /* UTC tm → time_t */
|
||||||
|
#define fsync(fd) _commit(fd) /* no fsync() on Windows; _commit() (<io.h>) is the equiv */
|
||||||
|
|
||||||
/* setenv/unsetenv: not in the Windows CRT; map to _putenv_s / SetEnvironmentVariable. */
|
/* setenv/unsetenv: not in the Windows CRT; map to _putenv_s / SetEnvironmentVariable. */
|
||||||
static inline int setenv(const char* name, const char* value, int overwrite) {
|
static inline int setenv(const char* name, const char* value, int overwrite) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -632,6 +632,7 @@ el_val_t engram_load(el_val_t path);
|
|||||||
* can pass results straight through without round-tripping ElList/ElMap
|
* can pass results straight through without round-tripping ElList/ElMap
|
||||||
* through json_stringify. */
|
* through json_stringify. */
|
||||||
el_val_t engram_get_node_json(el_val_t id);
|
el_val_t engram_get_node_json(el_val_t id);
|
||||||
|
el_val_t engram_get_node_by_label(el_val_t label);
|
||||||
el_val_t engram_search_json(el_val_t query, el_val_t limit);
|
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_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_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
||||||
|
|||||||
@@ -1072,6 +1072,7 @@ el_val_t __engram_save(el_val_t path) { return engram_save
|
|||||||
el_val_t __engram_load(el_val_t path) { return engram_load(path); }
|
el_val_t __engram_load(el_val_t path) { return engram_load(path); }
|
||||||
|
|
||||||
el_val_t __engram_get_node_json(el_val_t id) { return engram_get_node_json(id); }
|
el_val_t __engram_get_node_json(el_val_t id) { return engram_get_node_json(id); }
|
||||||
|
el_val_t __engram_get_node_by_label(el_val_t label) { return engram_get_node_by_label(label); }
|
||||||
|
|
||||||
el_val_t __engram_search_json(el_val_t query, el_val_t limit) {
|
el_val_t __engram_search_json(el_val_t query, el_val_t limit) {
|
||||||
return engram_search_json(query, limit);
|
return engram_search_json(query, limit);
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ 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_save(el_val_t path);
|
||||||
el_val_t __engram_load(el_val_t path);
|
el_val_t __engram_load(el_val_t path);
|
||||||
el_val_t __engram_get_node_json(el_val_t id);
|
el_val_t __engram_get_node_json(el_val_t id);
|
||||||
|
el_val_t __engram_get_node_by_label(el_val_t label);
|
||||||
el_val_t __engram_search_json(el_val_t query, el_val_t limit);
|
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_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_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
||||||
|
|||||||
@@ -2670,6 +2670,7 @@ fn builtin_arity(name: String) -> Int {
|
|||||||
if str_eq(name, "engram_save") { return 1 }
|
if str_eq(name, "engram_save") { return 1 }
|
||||||
if str_eq(name, "engram_load") { return 1 }
|
if str_eq(name, "engram_load") { return 1 }
|
||||||
if str_eq(name, "engram_get_node_json") { return 1 }
|
if str_eq(name, "engram_get_node_json") { return 1 }
|
||||||
|
if str_eq(name, "engram_get_node_by_label") { return 1 }
|
||||||
if str_eq(name, "engram_search_json") { return 2 }
|
if str_eq(name, "engram_search_json") { return 2 }
|
||||||
if str_eq(name, "engram_scan_nodes_json") { return 2 }
|
if str_eq(name, "engram_scan_nodes_json") { return 2 }
|
||||||
if str_eq(name, "engram_neighbors_json") { return 3 }
|
if str_eq(name, "engram_neighbors_json") { return 3 }
|
||||||
|
|||||||
@@ -23,10 +23,29 @@ fn tok_at(tokens: [Any], pos: Int) -> Map<String, Any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn tok_kind(tokens: [Any], pos: Int) -> String {
|
fn tok_kind(tokens: [Any], pos: Int) -> String {
|
||||||
|
// Out-of-range reads must report the Eof sentinel so every `== "Eof"`
|
||||||
|
// termination guard in the parser fires. Without this, reading past the
|
||||||
|
// single trailing Eof token returns runtime null (el_list_get OOB -> 0),
|
||||||
|
// which matches no delimiter, letting inner parse loops append AST nodes
|
||||||
|
// forever on malformed input -> unbounded allocation -> OOM.
|
||||||
|
let n: Int = native_list_len(tokens) / 2
|
||||||
|
if pos < 0 {
|
||||||
|
return "Eof"
|
||||||
|
}
|
||||||
|
if pos >= n {
|
||||||
|
return "Eof"
|
||||||
|
}
|
||||||
native_list_get(tokens, pos * 2)
|
native_list_get(tokens, pos * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tok_value(tokens: [Any], pos: Int) -> String {
|
fn tok_value(tokens: [Any], pos: Int) -> String {
|
||||||
|
let n: Int = native_list_len(tokens) / 2
|
||||||
|
if pos < 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if pos >= n {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
native_list_get(tokens, pos * 2 + 1)
|
native_list_get(tokens, pos * 2 + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +54,12 @@ fn expect(tokens: [Any], pos: Int, kind: String) -> Int {
|
|||||||
if k == kind {
|
if k == kind {
|
||||||
return pos + 1
|
return pos + 1
|
||||||
}
|
}
|
||||||
// On mismatch just advance; error recovery is best-effort
|
// On mismatch, error recovery is best-effort. But never step PAST the Eof
|
||||||
|
// sentinel: once at Eof a mismatch means the input ended early, and
|
||||||
|
// advancing would run the cursor off the token list.
|
||||||
|
if k == "Eof" {
|
||||||
|
return pos
|
||||||
|
}
|
||||||
pos + 1
|
pos + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
#ifndef EL_PLATFORM_WIN_H
|
||||||
|
#define EL_PLATFORM_WIN_H
|
||||||
|
/*
|
||||||
|
* el_platform_win.h — Windows OS-boundary shim for el_runtime.c.
|
||||||
|
*
|
||||||
|
* Branch: feat/windows-el-runtime. Included ONLY when _WIN32 is defined; the POSIX build is
|
||||||
|
* untouched. Goal: let el_runtime.c (a BSD-sockets / dlfcn / fork host) compile and link with
|
||||||
|
* mingw-w64 into a native neuron.exe, with no behavioural change to the Linux/macOS build.
|
||||||
|
*
|
||||||
|
* What it maps:
|
||||||
|
* - sockets : winsock2 (same call names: socket/bind/listen/accept/recv/send/setsockopt).
|
||||||
|
* Sockets close with closesocket() (see el_closesocket), and the stack must be
|
||||||
|
* started once with WSAStartup — done automatically via a load-time constructor.
|
||||||
|
* - dlsym : el_runtime.c uses dlsym(RTLD_DEFAULT, name) to resolve callback/tool symbols
|
||||||
|
* exported by the main module. Windows equivalent: GetProcAddress on the process
|
||||||
|
* module. Link the soul with -Wl,--export-all-symbols so the symbols are findable.
|
||||||
|
* - popen : mapped to _popen/_pclose.
|
||||||
|
* - threads : UNCHANGED. mingw-w64 ships winpthreads, so <pthread.h> + -lpthread just work.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <io.h>
|
||||||
|
#include <process.h>
|
||||||
|
|
||||||
|
/* Portable headers mingw-w64 provides (verified present). */
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h> /* strcasecmp */
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/time.h> /* mingw-w64 provides gettimeofday here */
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
/* ── socket close ─────────────────────────────────────────────────────────── */
|
||||||
|
/* Winsock closes sockets with closesocket(), not close() (close() is for file fds). The POSIX
|
||||||
|
build defines the same helper as close() so the call sites are identical across platforms. */
|
||||||
|
static inline int el_closesocket(SOCKET s) { return closesocket(s); }
|
||||||
|
|
||||||
|
/* ── setsockopt optval type ───────────────────────────────────────────────── */
|
||||||
|
/* Winsock's setsockopt takes optval as (const char*); POSIX takes (const void*), so el_runtime.c
|
||||||
|
passes &int directly. GCC 14+ makes that an error under -Wincompatible-pointer-types. Wrap it so
|
||||||
|
the runtime's POSIX-style call sites compile unchanged (defined before the macro so the wrapper
|
||||||
|
itself resolves to the real winsock setsockopt). */
|
||||||
|
static inline int el_setsockopt(SOCKET s, int level, int optname, const void* optval, int optlen) {
|
||||||
|
return setsockopt(s, level, optname, (const char*)optval, optlen);
|
||||||
|
}
|
||||||
|
#define setsockopt(s, l, o, v, n) el_setsockopt((s), (l), (o), (v), (int)(n))
|
||||||
|
|
||||||
|
/* ── winsock init (once, at load) ─────────────────────────────────────────── */
|
||||||
|
static void el__win_net_init(void) {
|
||||||
|
static int inited = 0;
|
||||||
|
if (!inited) { WSADATA w; WSAStartup(MAKEWORD(2, 2), &w); inited = 1; }
|
||||||
|
}
|
||||||
|
__attribute__((constructor)) static void el__win_ctor(void) { el__win_net_init(); }
|
||||||
|
|
||||||
|
/* ── dlsym → GetProcAddress ───────────────────────────────────────────────── */
|
||||||
|
#ifndef RTLD_DEFAULT
|
||||||
|
#define RTLD_DEFAULT ((void*)0)
|
||||||
|
#endif
|
||||||
|
static inline void* el_win_dlsym(void* handle, const char* name) {
|
||||||
|
(void)handle;
|
||||||
|
return (void*)(uintptr_t)GetProcAddress(GetModuleHandleA(NULL), name);
|
||||||
|
}
|
||||||
|
#define dlsym(h, n) el_win_dlsym((h), (n))
|
||||||
|
|
||||||
|
/* ── popen / pclose ───────────────────────────────────────────────────────── */
|
||||||
|
#define popen _popen
|
||||||
|
#define pclose _pclose
|
||||||
|
|
||||||
|
/* ── misc POSIX → Win32 shims ─────────────────────────────────────────────── */
|
||||||
|
#include <direct.h> /* _mkdir */
|
||||||
|
#define mkdir(path, mode) _mkdir(path) /* POSIX mkdir(path,mode) → _mkdir(path) */
|
||||||
|
#define timegm _mkgmtime /* UTC tm → time_t */
|
||||||
|
|
||||||
|
/* setenv/unsetenv: not in the Windows CRT; map to _putenv_s / SetEnvironmentVariable. */
|
||||||
|
static inline int setenv(const char* name, const char* value, int overwrite) {
|
||||||
|
(void)overwrite;
|
||||||
|
return _putenv_s(name, value ? value : "");
|
||||||
|
}
|
||||||
|
static inline int unsetenv(const char* name) {
|
||||||
|
/* _putenv_s(name, "") sets VAR="" rather than removing it.
|
||||||
|
* SetEnvironmentVariableA(name, NULL) truly deletes it from the Win32
|
||||||
|
* env block; then we sync the CRT cache with _putenv("NAME="). */
|
||||||
|
SetEnvironmentVariableA(name, NULL);
|
||||||
|
size_t len = strlen(name);
|
||||||
|
char *buf = (char*)malloc(len + 2);
|
||||||
|
if (!buf) return -1;
|
||||||
|
memcpy(buf, name, len);
|
||||||
|
buf[len] = '=';
|
||||||
|
buf[len + 1] = '\0';
|
||||||
|
_putenv(buf);
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* nanosleep — not available in MSVC/UCRT; approximate with Sleep(). */
|
||||||
|
static inline int el_nanosleep(const struct timespec *req, struct timespec *rem) {
|
||||||
|
(void)rem;
|
||||||
|
DWORD ms = (DWORD)((req->tv_sec * 1000ULL) + (req->tv_nsec / 1000000ULL));
|
||||||
|
Sleep(ms ? ms : 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#define nanosleep(req, rem) el_nanosleep((req), (rem))
|
||||||
|
|
||||||
|
/* localtime_r/gmtime_r: Windows offers localtime_s/gmtime_s with reversed arg order. */
|
||||||
|
static inline struct tm* localtime_r(const time_t* t, struct tm* out) {
|
||||||
|
return localtime_s(out, t) == 0 ? out : (struct tm*)0;
|
||||||
|
}
|
||||||
|
static inline struct tm* gmtime_r(const time_t* t, struct tm* out) {
|
||||||
|
return gmtime_s(out, t) == 0 ? out : (struct tm*)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── libcurl: degradable stubs for the curl-less Windows build ─────────────── */
|
||||||
|
/* The curl-less validation build (WITH_CURL=0) links no libcurl. el_runtime.c uses libcurl
|
||||||
|
* unconditionally for its HTTP client / LLM layer; these stubs let it compile and link so the
|
||||||
|
* runtime, HTTP *server*, graph and memory work natively on Windows. Live outbound HTTP/LLM calls
|
||||||
|
* degrade to a runtime error (curl_easy_perform returns an error) — matching the documented
|
||||||
|
* curl-less contract. When HAVE_CURL is defined (WITH_CURL=1) the real <curl/curl.h> is used and
|
||||||
|
* this whole block is compiled out. POSIX never sees this header, so the POSIX build is untouched. */
|
||||||
|
#ifndef HAVE_CURL
|
||||||
|
|
||||||
|
typedef void CURL;
|
||||||
|
typedef int CURLcode;
|
||||||
|
|
||||||
|
#define CURLE_OK 0
|
||||||
|
#define CURLE_HTTP_RETURNED_ERROR 22
|
||||||
|
#define CURL_ERROR_SIZE 256
|
||||||
|
|
||||||
|
/* Option ids: values are irrelevant to the no-op setopt below; kept distinct for readability. */
|
||||||
|
#define CURLOPT_URL 10002
|
||||||
|
#define CURLOPT_WRITEFUNCTION 20011
|
||||||
|
#define CURLOPT_WRITEDATA 10001
|
||||||
|
#define CURLOPT_POSTFIELDS 10015
|
||||||
|
#define CURLOPT_POSTFIELDSIZE 120
|
||||||
|
#define CURLOPT_POST 47
|
||||||
|
#define CURLOPT_HTTPHEADER 10023
|
||||||
|
#define CURLOPT_TIMEOUT_MS 155
|
||||||
|
#define CURLOPT_NOSIGNAL 99
|
||||||
|
#define CURLOPT_USERAGENT 10018
|
||||||
|
#define CURLOPT_FOLLOWLOCATION 52
|
||||||
|
#define CURLOPT_ERRORBUFFER 10010
|
||||||
|
#define CURLOPT_CUSTOMREQUEST 10036
|
||||||
|
#define CURLOPT_FAILONERROR 45
|
||||||
|
|
||||||
|
struct curl_slist { char* data; struct curl_slist* next; };
|
||||||
|
|
||||||
|
static inline struct curl_slist* curl_slist_append(struct curl_slist* list, const char* s) {
|
||||||
|
struct curl_slist* node = (struct curl_slist*)malloc(sizeof(struct curl_slist));
|
||||||
|
if (!node) return list;
|
||||||
|
node->data = s ? strdup(s) : NULL;
|
||||||
|
node->next = NULL;
|
||||||
|
if (!list) return node;
|
||||||
|
struct curl_slist* p = list;
|
||||||
|
while (p->next) p = p->next;
|
||||||
|
p->next = node;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
static inline void curl_slist_free_all(struct curl_slist* list) {
|
||||||
|
while (list) { struct curl_slist* n = list->next; free(list->data); free(list); list = n; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CURL* curl_easy_init(void) { return (CURL*)malloc(1); }
|
||||||
|
static inline CURLcode curl_easy_setopt(CURL* h, int opt, ...) { (void)h; (void)opt; return CURLE_OK; }
|
||||||
|
static inline CURLcode curl_easy_perform(CURL* h) { (void)h; return 7 /* CURLE_COULDNT_CONNECT */; }
|
||||||
|
static inline void curl_easy_cleanup(CURL* h) { free(h); }
|
||||||
|
static inline const char* curl_easy_strerror(CURLcode c) {
|
||||||
|
(void)c; return "libcurl not built in (curl-less build)";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* !HAVE_CURL */
|
||||||
|
|
||||||
|
#endif /* EL_PLATFORM_WIN_H */
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -758,6 +758,18 @@ el_val_t trace_span_start(el_val_t name);
|
|||||||
el_val_t trace_span_end(el_val_t span_handle);
|
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);
|
el_val_t emit_event(el_val_t name, el_val_t duration_ms);
|
||||||
|
|
||||||
|
/* ── Runtime symbols required by the soul modules ──────────────────────────── */
|
||||||
|
/* All implemented in el_runtime.c but omitted from this release header; the soul dist modules
|
||||||
|
* reference them directly, so the public header must export them. Declarations only — mirrors the
|
||||||
|
* mainline el_runtime.h and is platform-independent (no behavioural change to the POSIX build). */
|
||||||
|
typedef el_val_t (*http_handler_fn)(el_val_t method, el_val_t path, el_val_t body);
|
||||||
|
typedef el_val_t (*http_handler4_fn)(el_val_t method, el_val_t path, el_val_t body, el_val_t headers);
|
||||||
|
el_val_t el_arena_push(void);
|
||||||
|
el_val_t el_arena_pop(el_val_t mark);
|
||||||
|
void http_serve_async(el_val_t port, el_val_t handler);
|
||||||
|
el_val_t engram_get_node_by_label(el_val_t label);
|
||||||
|
el_val_t engram_prune_telemetry(el_val_t older_than_ms);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user