Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a72fced28 | |||
| 791b0880b7 | |||
| 23552ed40a | |||
| 6838e5cbff | |||
| fa2b49365b | |||
| 971b21751a | |||
| 9f1db8278c | |||
| 3d05e0c2a9 | |||
| 3bf44dee2d | |||
| a43a35bd10 | |||
| afc92f4e33 | |||
| 005e84e5d3 | |||
| 7f03876e26 | |||
| 599073cb92 | |||
| 7f66529510 | |||
| 6ebe3d0d66 | |||
| 9f362c90e5 | |||
| 11dc138a93 | |||
| 227f158a05 | |||
| 97e484221d | |||
| 8f8ccc945e | |||
| 409ec99397 | |||
| dc39a61e2c | |||
| eba9eac8a8 | |||
| ab6b52a0b4 | |||
| e3dabe3e08 | |||
| 0a0a2bcb44 |
@@ -39,9 +39,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c
|
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c
|
||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I el-compiler/runtime \
|
-I runtime \
|
||||||
dist/elc-gen2.c \
|
dist/elc-gen2.c \
|
||||||
el-compiler/runtime/el_runtime.c \
|
runtime/el_runtime.c \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||||
-o dist/platform/elc
|
-o dist/platform/elc
|
||||||
chmod +x dist/platform/elc
|
chmod +x dist/platform/elc
|
||||||
@@ -54,9 +54,9 @@ jobs:
|
|||||||
mkdir -p dist/bin
|
mkdir -p dist/bin
|
||||||
dist/platform/elc elb.el > dist/elb.c
|
dist/platform/elc elb.el > dist/elb.c
|
||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I el-compiler/runtime \
|
-I runtime \
|
||||||
dist/elb.c \
|
dist/elb.c \
|
||||||
el-compiler/runtime/el_runtime.c \
|
runtime/el_runtime.c \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||||
-o dist/bin/elb
|
-o dist/bin/elb
|
||||||
chmod +x dist/bin/elb
|
chmod +x dist/bin/elb
|
||||||
@@ -91,7 +91,7 @@ jobs:
|
|||||||
- name: Precompile el_runtime.o
|
- name: Precompile el_runtime.o
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
gcc -O2 -c -I "$RUNTIME" "$RUNTIME/el_runtime.c" \
|
gcc -O2 -c -I "$RUNTIME" "$RUNTIME/el_runtime.c" \
|
||||||
-o /tmp/el_runtime.o
|
-o /tmp/el_runtime.o
|
||||||
echo "el_runtime.o compiled"
|
echo "el_runtime.o compiled"
|
||||||
@@ -100,7 +100,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_core.el > /tmp/el_native_core.c
|
"$ELC" --test tests/native/test_core.el > /tmp/el_native_core.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c /tmp/el_runtime.o \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c /tmp/el_runtime.o \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_text.el > /tmp/el_native_text.c
|
"$ELC" --test tests/native/test_text.el > /tmp/el_native_text.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c /tmp/el_runtime.o \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c /tmp/el_runtime.o \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
||||||
@@ -120,7 +120,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_string.el > /tmp/el_native_string.c
|
"$ELC" --test tests/native/test_string.el > /tmp/el_native_string.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c /tmp/el_runtime.o \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c /tmp/el_runtime.o \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
||||||
@@ -130,7 +130,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_math.el > /tmp/el_native_math.c
|
"$ELC" --test tests/native/test_math.el > /tmp/el_native_math.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c /tmp/el_runtime.o \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c /tmp/el_runtime.o \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
||||||
@@ -140,7 +140,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_state.el > /tmp/el_native_state.c
|
"$ELC" --test tests/native/test_state.el > /tmp/el_native_state.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c /tmp/el_runtime.o \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c /tmp/el_runtime.o \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
||||||
@@ -150,7 +150,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_time.el > /tmp/el_native_time.c
|
"$ELC" --test tests/native/test_time.el > /tmp/el_native_time.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c /tmp/el_runtime.o \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c /tmp/el_runtime.o \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
||||||
@@ -160,7 +160,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_json.el > /tmp/el_native_json.c
|
"$ELC" --test tests/native/test_json.el > /tmp/el_native_json.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c /tmp/el_runtime.o \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c /tmp/el_runtime.o \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
||||||
@@ -170,7 +170,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_env.el > /tmp/el_native_env.c
|
"$ELC" --test tests/native/test_env.el > /tmp/el_native_env.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c /tmp/el_runtime.o \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c /tmp/el_runtime.o \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
||||||
@@ -180,7 +180,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_fs.el > /tmp/el_native_fs.c
|
"$ELC" --test tests/native/test_fs.el > /tmp/el_native_fs.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c /tmp/el_runtime.o \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c /tmp/el_runtime.o \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
||||||
@@ -191,7 +191,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
ABS_RUNTIME="$(pwd)/runtime"
|
||||||
ABS_OUT="$(pwd)/dist/bin"
|
ABS_OUT="$(pwd)/dist/bin"
|
||||||
(cd ../epm && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
(cd ../epm && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||||
chmod +x dist/bin/epm
|
chmod +x dist/bin/epm
|
||||||
@@ -202,7 +202,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
ABS_RUNTIME="$(pwd)/runtime"
|
||||||
ABS_OUT="$(pwd)/dist/bin"
|
ABS_OUT="$(pwd)/dist/bin"
|
||||||
(cd tools/install && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
(cd tools/install && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||||
chmod +x dist/bin/el-install
|
chmod +x dist/bin/el-install
|
||||||
@@ -242,7 +242,7 @@ jobs:
|
|||||||
--project=neuron-785695 \
|
--project=neuron-785695 \
|
||||||
--package=el-runtime-c \
|
--package=el-runtime-c \
|
||||||
--version="${VERSION}" \
|
--version="${VERSION}" \
|
||||||
--source=el-compiler/runtime/el_runtime.c
|
--source=runtime/el_runtime.c
|
||||||
|
|
||||||
gcloud artifacts generic upload \
|
gcloud artifacts generic upload \
|
||||||
--repository=foundation-dev \
|
--repository=foundation-dev \
|
||||||
@@ -250,7 +250,7 @@ jobs:
|
|||||||
--project=neuron-785695 \
|
--project=neuron-785695 \
|
||||||
--package=el-runtime-h \
|
--package=el-runtime-h \
|
||||||
--version="${VERSION}" \
|
--version="${VERSION}" \
|
||||||
--source=el-compiler/runtime/el_runtime.h
|
--source=runtime/el_runtime.h
|
||||||
|
|
||||||
gcloud artifacts generic upload \
|
gcloud artifacts generic upload \
|
||||||
--repository=foundation-dev \
|
--repository=foundation-dev \
|
||||||
@@ -258,7 +258,7 @@ jobs:
|
|||||||
--project=neuron-785695 \
|
--project=neuron-785695 \
|
||||||
--package=el-runtime-js \
|
--package=el-runtime-js \
|
||||||
--version="${VERSION}" \
|
--version="${VERSION}" \
|
||||||
--source=el-compiler/runtime/el_runtime.js
|
--source=runtime/el_runtime.js
|
||||||
|
|
||||||
echo "Published El SDK version=${VERSION} to foundation-dev"
|
echo "Published El SDK version=${VERSION} to foundation-dev"
|
||||||
# Keep key alive for the ci-base rebuild step below
|
# Keep key alive for the ci-base rebuild step below
|
||||||
@@ -291,9 +291,9 @@ jobs:
|
|||||||
FROM ${BASE}
|
FROM ${BASE}
|
||||||
COPY dist/platform/elc /opt/el/dist/platform/elc
|
COPY dist/platform/elc /opt/el/dist/platform/elc
|
||||||
COPY dist/bin/elb /opt/el/dist/bin/elb
|
COPY dist/bin/elb /opt/el/dist/bin/elb
|
||||||
COPY el-compiler/runtime/el_runtime.c /opt/el/el-compiler/runtime/el_runtime.c
|
COPY runtime/el_runtime.c /opt/el/runtime/el_runtime.c
|
||||||
COPY el-compiler/runtime/el_runtime.h /opt/el/el-compiler/runtime/el_runtime.h
|
COPY runtime/el_runtime.h /opt/el/runtime/el_runtime.h
|
||||||
COPY el-compiler/runtime/el_runtime.js /opt/el/el-compiler/runtime/el_runtime.js
|
COPY runtime/el_runtime.js /opt/el/runtime/el_runtime.js
|
||||||
RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c
|
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c
|
||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I el-compiler/runtime \
|
-I runtime \
|
||||||
dist/elc-gen2.c \
|
dist/elc-gen2.c \
|
||||||
el-compiler/runtime/el_runtime.c \
|
runtime/el_runtime.c \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||||
-o dist/platform/elc
|
-o dist/platform/elc
|
||||||
chmod +x dist/platform/elc
|
chmod +x dist/platform/elc
|
||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_core.el > /tmp/el_native_core.c
|
"$ELC" --test tests/native/test_core.el > /tmp/el_native_core.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_text.el > /tmp/el_native_text.c
|
"$ELC" --test tests/native/test_text.el > /tmp/el_native_text.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
||||||
@@ -104,7 +104,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_string.el > /tmp/el_native_string.c
|
"$ELC" --test tests/native/test_string.el > /tmp/el_native_string.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_math.el > /tmp/el_native_math.c
|
"$ELC" --test tests/native/test_math.el > /tmp/el_native_math.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
||||||
@@ -124,7 +124,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_state.el > /tmp/el_native_state.c
|
"$ELC" --test tests/native/test_state.el > /tmp/el_native_state.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_time.el > /tmp/el_native_time.c
|
"$ELC" --test tests/native/test_time.el > /tmp/el_native_time.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
||||||
@@ -144,7 +144,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_json.el > /tmp/el_native_json.c
|
"$ELC" --test tests/native/test_json.el > /tmp/el_native_json.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
||||||
@@ -154,7 +154,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_env.el > /tmp/el_native_env.c
|
"$ELC" --test tests/native/test_env.el > /tmp/el_native_env.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
||||||
@@ -164,7 +164,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_fs.el > /tmp/el_native_fs.c
|
"$ELC" --test tests/native/test_fs.el > /tmp/el_native_fs.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
||||||
@@ -176,9 +176,9 @@ jobs:
|
|||||||
mkdir -p dist/bin
|
mkdir -p dist/bin
|
||||||
dist/platform/elc elb.el > dist/elb.c
|
dist/platform/elc elb.el > dist/elb.c
|
||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I el-compiler/runtime \
|
-I runtime \
|
||||||
dist/elb.c \
|
dist/elb.c \
|
||||||
el-compiler/runtime/el_runtime.c \
|
runtime/el_runtime.c \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||||
-o dist/bin/elb
|
-o dist/bin/elb
|
||||||
chmod +x dist/bin/elb
|
chmod +x dist/bin/elb
|
||||||
@@ -189,7 +189,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
ABS_RUNTIME="$(pwd)/runtime"
|
||||||
ABS_OUT="$(pwd)/dist/bin"
|
ABS_OUT="$(pwd)/dist/bin"
|
||||||
(cd ../epm && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
(cd ../epm && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||||
chmod +x dist/bin/epm
|
chmod +x dist/bin/epm
|
||||||
@@ -200,7 +200,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
ABS_RUNTIME="$(pwd)/runtime"
|
||||||
ABS_OUT="$(pwd)/dist/bin"
|
ABS_OUT="$(pwd)/dist/bin"
|
||||||
(cd tools/install && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
(cd tools/install && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||||
chmod +x dist/bin/el-install
|
chmod +x dist/bin/el-install
|
||||||
@@ -235,7 +235,7 @@ jobs:
|
|||||||
--project=neuron-785695 \
|
--project=neuron-785695 \
|
||||||
--package=el-runtime-c \
|
--package=el-runtime-c \
|
||||||
--version="${VERSION}" \
|
--version="${VERSION}" \
|
||||||
--source=el-compiler/runtime/el_runtime.c
|
--source=runtime/el_runtime.c
|
||||||
|
|
||||||
gcloud artifacts generic upload \
|
gcloud artifacts generic upload \
|
||||||
--repository=foundation-stage \
|
--repository=foundation-stage \
|
||||||
@@ -243,7 +243,7 @@ jobs:
|
|||||||
--project=neuron-785695 \
|
--project=neuron-785695 \
|
||||||
--package=el-runtime-h \
|
--package=el-runtime-h \
|
||||||
--version="${VERSION}" \
|
--version="${VERSION}" \
|
||||||
--source=el-compiler/runtime/el_runtime.h
|
--source=runtime/el_runtime.h
|
||||||
|
|
||||||
echo "Published El SDK version=${VERSION} to foundation-stage"
|
echo "Published El SDK version=${VERSION} to foundation-stage"
|
||||||
# Keep key alive for the ci-base rebuild step below
|
# Keep key alive for the ci-base rebuild step below
|
||||||
@@ -275,9 +275,9 @@ jobs:
|
|||||||
FROM ${BASE}
|
FROM ${BASE}
|
||||||
COPY dist/platform/elc /opt/el/dist/platform/elc
|
COPY dist/platform/elc /opt/el/dist/platform/elc
|
||||||
COPY dist/bin/elb /opt/el/dist/bin/elb
|
COPY dist/bin/elb /opt/el/dist/bin/elb
|
||||||
COPY el-compiler/runtime/el_runtime.c /opt/el/el-compiler/runtime/el_runtime.c
|
COPY runtime/el_runtime.c /opt/el/runtime/el_runtime.c
|
||||||
COPY el-compiler/runtime/el_runtime.h /opt/el/el-compiler/runtime/el_runtime.h
|
COPY runtime/el_runtime.h /opt/el/runtime/el_runtime.h
|
||||||
COPY el-compiler/runtime/el_runtime.js /opt/el/el-compiler/runtime/el_runtime.js
|
COPY runtime/el_runtime.js /opt/el/runtime/el_runtime.js
|
||||||
RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ jobs:
|
|||||||
mkdir -p dist/platform
|
mkdir -p dist/platform
|
||||||
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c
|
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c
|
||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I el-compiler/runtime \
|
-I runtime \
|
||||||
dist/elc-gen2.c \
|
dist/elc-gen2.c \
|
||||||
el-compiler/runtime/el_runtime.c \
|
runtime/el_runtime.c \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||||
-o dist/platform/elc
|
-o dist/platform/elc
|
||||||
chmod +x dist/platform/elc
|
chmod +x dist/platform/elc
|
||||||
@@ -62,9 +62,9 @@ jobs:
|
|||||||
mkdir -p dist/bin
|
mkdir -p dist/bin
|
||||||
dist/platform/elc elb.el > dist/elb.c
|
dist/platform/elc elb.el > dist/elb.c
|
||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I el-compiler/runtime \
|
-I runtime \
|
||||||
dist/elb.c \
|
dist/elb.c \
|
||||||
el-compiler/runtime/el_runtime.c \
|
runtime/el_runtime.c \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||||
-o dist/bin/elb
|
-o dist/bin/elb
|
||||||
chmod +x dist/bin/elb
|
chmod +x dist/bin/elb
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
ABS_RUNTIME="$(pwd)/runtime"
|
||||||
ABS_OUT="$(pwd)/dist/bin"
|
ABS_OUT="$(pwd)/dist/bin"
|
||||||
(cd ../epm && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
(cd ../epm && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||||
chmod +x dist/bin/epm
|
chmod +x dist/bin/epm
|
||||||
@@ -86,7 +86,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
ABS_ELB="$(pwd)/dist/bin/elb"
|
ABS_ELB="$(pwd)/dist/bin/elb"
|
||||||
ABS_ELC="$(pwd)/dist/platform/elc"
|
ABS_ELC="$(pwd)/dist/platform/elc"
|
||||||
ABS_RUNTIME="$(pwd)/el-compiler/runtime"
|
ABS_RUNTIME="$(pwd)/runtime"
|
||||||
ABS_OUT="$(pwd)/dist/bin"
|
ABS_OUT="$(pwd)/dist/bin"
|
||||||
(cd tools/install && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
(cd tools/install && "$ABS_ELB" --clean --elc="$ABS_ELC" --runtime="$ABS_RUNTIME" --out="$ABS_OUT")
|
||||||
chmod +x dist/bin/el-install
|
chmod +x dist/bin/el-install
|
||||||
@@ -121,7 +121,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_core.el > /tmp/el_native_core.c
|
"$ELC" --test tests/native/test_core.el > /tmp/el_native_core.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_core.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
||||||
@@ -131,7 +131,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_text.el > /tmp/el_native_text.c
|
"$ELC" --test tests/native/test_text.el > /tmp/el_native_text.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_text.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
||||||
@@ -141,7 +141,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_string.el > /tmp/el_native_string.c
|
"$ELC" --test tests/native/test_string.el > /tmp/el_native_string.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_string.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
||||||
@@ -151,7 +151,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_math.el > /tmp/el_native_math.c
|
"$ELC" --test tests/native/test_math.el > /tmp/el_native_math.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_math.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
||||||
@@ -161,7 +161,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_state.el > /tmp/el_native_state.c
|
"$ELC" --test tests/native/test_state.el > /tmp/el_native_state.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_state.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
||||||
@@ -171,7 +171,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_time.el > /tmp/el_native_time.c
|
"$ELC" --test tests/native/test_time.el > /tmp/el_native_time.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_time.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
||||||
@@ -181,7 +181,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_json.el > /tmp/el_native_json.c
|
"$ELC" --test tests/native/test_json.el > /tmp/el_native_json.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_json.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
||||||
@@ -191,7 +191,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_env.el > /tmp/el_native_env.c
|
"$ELC" --test tests/native/test_env.el > /tmp/el_native_env.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_env.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
||||||
@@ -201,7 +201,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/el-compiler/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
"$ELC" --test tests/native/test_fs.el > /tmp/el_native_fs.c
|
"$ELC" --test tests/native/test_fs.el > /tmp/el_native_fs.c
|
||||||
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c "$RUNTIME/el_runtime.c" \
|
gcc -O2 -I "$RUNTIME" /tmp/el_native_fs.c "$RUNTIME/el_runtime.c" \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
||||||
@@ -216,8 +216,8 @@ jobs:
|
|||||||
cp lang/dist/platform/elc dist/sdk/bin/elc
|
cp lang/dist/platform/elc dist/sdk/bin/elc
|
||||||
cp lang/dist/bin/elb dist/sdk/bin/elb
|
cp lang/dist/bin/elb dist/sdk/bin/elb
|
||||||
cp lang/dist/bin/epm dist/sdk/bin/epm
|
cp lang/dist/bin/epm dist/sdk/bin/epm
|
||||||
cp lang/el-compiler/runtime/el_runtime.c dist/sdk/runtime/
|
cp lang/runtime/el_runtime.c dist/sdk/runtime/
|
||||||
cp lang/el-compiler/runtime/el_runtime.h dist/sdk/runtime/
|
cp lang/runtime/el_runtime.h dist/sdk/runtime/
|
||||||
cp lang/runtime/*.el dist/sdk/runtime/
|
cp lang/runtime/*.el dist/sdk/runtime/
|
||||||
tar -czf dist/el-sdk-latest.tar.gz -C dist/sdk .
|
tar -czf dist/el-sdk-latest.tar.gz -C dist/sdk .
|
||||||
echo "SDK tarball bundled: dist/el-sdk-latest.tar.gz"
|
echo "SDK tarball bundled: dist/el-sdk-latest.tar.gz"
|
||||||
@@ -274,8 +274,8 @@ jobs:
|
|||||||
|
|
||||||
# Per-file assets (downstream CI needs these individually)
|
# Per-file assets (downstream CI needs these individually)
|
||||||
upload_asset lang/dist/platform/elc elc
|
upload_asset lang/dist/platform/elc elc
|
||||||
upload_asset lang/el-compiler/runtime/el_runtime.c el_runtime.c
|
upload_asset lang/runtime/el_runtime.c el_runtime.c
|
||||||
upload_asset lang/el-compiler/runtime/el_runtime.h el_runtime.h
|
upload_asset lang/runtime/el_runtime.h el_runtime.h
|
||||||
|
|
||||||
# SDK bundle and installer binary
|
# SDK bundle and installer binary
|
||||||
upload_asset dist/el-sdk-latest.tar.gz el-sdk-latest.tar.gz
|
upload_asset dist/el-sdk-latest.tar.gz el-sdk-latest.tar.gz
|
||||||
@@ -319,7 +319,7 @@ jobs:
|
|||||||
--project=neuron-785695 \
|
--project=neuron-785695 \
|
||||||
--package=el-runtime-c \
|
--package=el-runtime-c \
|
||||||
--version="${VERSION}" \
|
--version="${VERSION}" \
|
||||||
--source=el-compiler/runtime/el_runtime.c
|
--source=runtime/el_runtime.c
|
||||||
|
|
||||||
gcloud artifacts generic upload \
|
gcloud artifacts generic upload \
|
||||||
--repository=foundation-prod \
|
--repository=foundation-prod \
|
||||||
@@ -327,7 +327,7 @@ jobs:
|
|||||||
--project=neuron-785695 \
|
--project=neuron-785695 \
|
||||||
--package=el-runtime-h \
|
--package=el-runtime-h \
|
||||||
--version="${VERSION}" \
|
--version="${VERSION}" \
|
||||||
--source=el-compiler/runtime/el_runtime.h
|
--source=runtime/el_runtime.h
|
||||||
|
|
||||||
gcloud artifacts generic upload \
|
gcloud artifacts generic upload \
|
||||||
--repository=foundation-prod \
|
--repository=foundation-prod \
|
||||||
@@ -335,7 +335,7 @@ jobs:
|
|||||||
--project=neuron-785695 \
|
--project=neuron-785695 \
|
||||||
--package=el-runtime-js \
|
--package=el-runtime-js \
|
||||||
--version="${VERSION}" \
|
--version="${VERSION}" \
|
||||||
--source=el-compiler/runtime/el_runtime.js
|
--source=runtime/el_runtime.js
|
||||||
|
|
||||||
echo "Published El SDK version=${VERSION} to foundation-prod"
|
echo "Published El SDK version=${VERSION} to foundation-prod"
|
||||||
# Keep key alive for the ci-base rebuild step below
|
# Keep key alive for the ci-base rebuild step below
|
||||||
@@ -367,9 +367,9 @@ jobs:
|
|||||||
FROM ${BASE}
|
FROM ${BASE}
|
||||||
COPY dist/platform/elc /opt/el/dist/platform/elc
|
COPY dist/platform/elc /opt/el/dist/platform/elc
|
||||||
COPY dist/bin/elb /opt/el/dist/bin/elb
|
COPY dist/bin/elb /opt/el/dist/bin/elb
|
||||||
COPY el-compiler/runtime/el_runtime.c /opt/el/el-compiler/runtime/el_runtime.c
|
COPY runtime/el_runtime.c /opt/el/runtime/el_runtime.c
|
||||||
COPY el-compiler/runtime/el_runtime.h /opt/el/el-compiler/runtime/el_runtime.h
|
COPY runtime/el_runtime.h /opt/el/runtime/el_runtime.h
|
||||||
COPY el-compiler/runtime/el_runtime.js /opt/el/el-compiler/runtime/el_runtime.js
|
COPY runtime/el_runtime.js /opt/el/runtime/el_runtime.js
|
||||||
RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ set -euo pipefail
|
|||||||
|
|
||||||
ROOT="$(git rev-parse --show-toplevel)"
|
ROOT="$(git rev-parse --show-toplevel)"
|
||||||
LANG_DIR="$ROOT/lang"
|
LANG_DIR="$ROOT/lang"
|
||||||
RUNTIME="$LANG_DIR/el-compiler/runtime"
|
RUNTIME="$LANG_DIR/runtime"
|
||||||
ELC="$LANG_DIR/dist/platform/elc"
|
ELC="$LANG_DIR/dist/platform/elc"
|
||||||
|
|
||||||
# If elc isn't built yet, skip with a warning rather than blocking
|
# If elc isn't built yet, skip with a warning rather than blocking
|
||||||
if [ ! -x "$ELC" ]; then
|
if [ ! -x "$ELC" ]; then
|
||||||
echo "⚠ elc not found at lang/dist/platform/elc — skipping pre-commit tests"
|
echo "⚠ elc not found at lang/dist/platform/elc — skipping pre-commit tests"
|
||||||
echo " Build it first: cd lang && gcc -O2 -I el-compiler/runtime dist/elc-bootstrap.c el-compiler/runtime/el_runtime.c -lcurl -lpthread -o dist/elc-gen2 && ./dist/elc-gen2 el-compiler/src/compiler.el > /tmp/elc.c && gcc -O2 -I el-compiler/runtime /tmp/elc.c el-compiler/runtime/el_runtime.c -lcurl -lpthread -o dist/platform/elc"
|
echo " Build it first: cd lang && gcc -O2 -I runtime dist/elc-bootstrap.c runtime/el_runtime.c -lcurl -lpthread -o dist/elc-gen2 && ./dist/elc-gen2 el-compiler/src/compiler.el > /tmp/elc.c && gcc -O2 -I runtime /tmp/elc.c runtime/el_runtime.c -lcurl -lpthread -o dist/platform/elc"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ cd "$(dirname "$0")"
|
|||||||
|
|
||||||
EL_HOME="${EL_HOME:-$(cd ../.. && pwd)/el}"
|
EL_HOME="${EL_HOME:-$(cd ../.. && pwd)/el}"
|
||||||
ELC="${ELC:-${EL_HOME}/dist/platform/elc}"
|
ELC="${ELC:-${EL_HOME}/dist/platform/elc}"
|
||||||
RUNTIME_DIR="${EL_HOME}/el-compiler/runtime"
|
RUNTIME_DIR="${EL_HOME}/runtime"
|
||||||
SRC_DIR="$(cd .. && pwd)/src"
|
SRC_DIR="$(cd .. && pwd)/src"
|
||||||
|
|
||||||
if [ ! -x "${ELC}" ]; then
|
if [ ! -x "${ELC}" ]; then
|
||||||
|
|||||||
+5
-2
@@ -1,3 +1,6 @@
|
|||||||
target/
|
|
||||||
*.db
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*.db
|
||||||
|
*.elc
|
||||||
|
*.elh
|
||||||
|
dist/
|
||||||
|
target/
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
+254
-105
@@ -10,6 +10,9 @@ 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 route_act_stats(el_val_t method, el_val_t path, el_val_t body);
|
||||||
|
el_val_t route_text_health(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);
|
||||||
@@ -17,21 +20,29 @@ 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 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 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 route_create_edges_batch(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_embed_backfill(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 route_similarity(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 +121,40 @@ el_val_t route_stats(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
el_val_t route_act_stats(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
|
return engram_act_stats_json();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
el_val_t route_text_health(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
|
return engram_text_health_json();
|
||||||
|
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; });
|
||||||
|
return engram_save(el_str_concat(dir, EL_STR("/snapshot.json")));
|
||||||
|
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_present = json_get_raw(body, EL_STR("salience"));
|
||||||
}
|
el_val_t salience = ({ el_val_t _if_result_3 = 0; if (str_eq(sal_present, EL_STR(""))) { _if_result_3 = (el_from_float(0.5)); } else { _if_result_3 = (json_get_float(body, EL_STR("salience"))); } _if_result_3; });
|
||||||
el_val_t salience = json_get_float(body, EL_STR("salience"));
|
el_val_t label_raw = json_get_string(body, EL_STR("label"));
|
||||||
if (salience == el_from_float(0.0)) {
|
el_val_t label = ({ el_val_t _if_result_4 = 0; if (str_eq(label_raw, EL_STR(""))) { _if_result_4 = (content); } else { _if_result_4 = (label_raw); } _if_result_4; });
|
||||||
salience = el_from_float(0.5);
|
el_val_t imp_present = json_get_raw(body, EL_STR("importance"));
|
||||||
}
|
el_val_t importance = ({ el_val_t _if_result_5 = 0; if (str_eq(imp_present, EL_STR(""))) { _if_result_5 = (el_from_float(0.5)); } else { _if_result_5 = (json_get_float(body, EL_STR("importance"))); } _if_result_5; });
|
||||||
el_val_t id = engram_node(content, node_type, salience);
|
el_val_t conf_present = json_get_raw(body, EL_STR("confidence"));
|
||||||
|
el_val_t confidence = ({ el_val_t _if_result_6 = 0; if (str_eq(conf_present, EL_STR(""))) { _if_result_6 = (el_from_float(1.0)); } else { _if_result_6 = (json_get_float(body, EL_STR("confidence"))); } _if_result_6; });
|
||||||
|
el_val_t tier_raw = json_get_string(body, EL_STR("tier"));
|
||||||
|
el_val_t tier = ({ el_val_t _if_result_7 = 0; if (str_eq(tier_raw, EL_STR(""))) { _if_result_7 = (EL_STR("Working")); } else { _if_result_7 = (tier_raw); } _if_result_7; });
|
||||||
|
el_val_t tags = json_get_string(body, EL_STR("tags"));
|
||||||
|
el_val_t id = engram_node_full(content, node_type, label, salience, importance, confidence, tier, tags);
|
||||||
|
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 +180,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_8 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_8 = (EL_STR("/tmp/engram")); } else { _if_result_8 = (dir_raw); } _if_result_8; });
|
||||||
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 +197,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_9 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_9 = (query_param(path, EL_STR("q"))); } else { _if_result_9 = (json_get_string(body, EL_STR("query"))); } _if_result_9; });
|
||||||
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_10 = 0; if ((lim_url > 0)) { _if_result_10 = (lim_url); } else { _if_result_10 = (lim_body); } _if_result_10; });
|
||||||
q = json_get_string(body, EL_STR("query"));
|
el_val_t limit = ({ el_val_t _if_result_11 = 0; if ((lim_either > 0)) { _if_result_11 = (lim_either); } else { _if_result_11 = (20); } _if_result_11; });
|
||||||
}
|
|
||||||
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_12 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_12 = (query_param(path, EL_STR("q"))); } else { _if_result_12 = (json_get_string(body, EL_STR("query"))); } _if_result_12; });
|
||||||
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_13 = 0; if (str_eq(method, EL_STR("GET"))) { _if_result_13 = (query_int(path, EL_STR("depth"), 3)); } else { _if_result_13 = (json_get_int(body, EL_STR("depth"))); } _if_result_13; });
|
||||||
|
el_val_t depth = ({ el_val_t _if_result_14 = 0; if ((d_raw > 0)) { _if_result_14 = (d_raw); } else { _if_result_14 = (3); } _if_result_14; });
|
||||||
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,19 +220,51 @@ 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_15 = 0; if (str_eq(rel_raw, EL_STR(""))) { _if_result_15 = (EL_STR("associates")); } else { _if_result_15 = (rel_raw); } _if_result_15; });
|
||||||
relation = EL_STR("associates");
|
el_val_t w_present = json_get_raw(body, EL_STR("weight"));
|
||||||
}
|
el_val_t weight = ({ el_val_t _if_result_16 = 0; if (str_eq(w_present, EL_STR(""))) { _if_result_16 = (el_from_float(0.5)); } else { _if_result_16 = (json_get_float(body, EL_STR("weight"))); } _if_result_16; });
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
el_val_t route_create_edges_batch(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
|
el_val_t arr = json_get_raw(body, EL_STR("edges"));
|
||||||
|
if (str_eq(arr, EL_STR(""))) {
|
||||||
|
return err_json(EL_STR("missing edges array"));
|
||||||
|
}
|
||||||
|
el_val_t n = json_array_len(arr);
|
||||||
|
if (n == 0) {
|
||||||
|
return EL_STR("{\"ok\":true,\"accepted\":0,\"skipped\":0}");
|
||||||
|
}
|
||||||
|
el_val_t i = 0;
|
||||||
|
el_val_t accepted = 0;
|
||||||
|
el_val_t skipped = 0;
|
||||||
|
while (i < n) {
|
||||||
|
el_val_t item = json_array_get(arr, i);
|
||||||
|
el_val_t from_id = json_get_string(item, EL_STR("from_id"));
|
||||||
|
el_val_t to_id = json_get_string(item, EL_STR("to_id"));
|
||||||
|
if (str_eq(from_id, EL_STR("")) || str_eq(to_id, EL_STR(""))) {
|
||||||
|
skipped = (skipped + 1);
|
||||||
|
} else {
|
||||||
|
el_val_t rel_raw = json_get_string(item, EL_STR("relation"));
|
||||||
|
el_val_t relation = ({ el_val_t _if_result_17 = 0; if (str_eq(rel_raw, EL_STR(""))) { _if_result_17 = (EL_STR("associates")); } else { _if_result_17 = (rel_raw); } _if_result_17; });
|
||||||
|
el_val_t w_present = json_get_raw(item, EL_STR("weight"));
|
||||||
|
el_val_t weight = ({ el_val_t _if_result_18 = 0; if (str_eq(w_present, EL_STR(""))) { _if_result_18 = (el_from_float(0.5)); } else { _if_result_18 = (json_get_float(item, EL_STR("weight"))); } _if_result_18; });
|
||||||
|
engram_connect(from_id, to_id, weight, relation);
|
||||||
|
accepted = (accepted + 1);
|
||||||
|
}
|
||||||
|
i = (i + 1);
|
||||||
|
}
|
||||||
|
if (accepted > 0) {
|
||||||
|
el_val_t saved = persist_canonical();
|
||||||
|
}
|
||||||
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"accepted\":"), int_to_str(accepted)), EL_STR(",\"skipped\":")), int_to_str(skipped)), EL_STR("}"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
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 id = extract_id(path, EL_STR("/api/neighbors/"));
|
el_val_t id = extract_id(path, EL_STR("/api/neighbors/"));
|
||||||
if (str_eq(id, EL_STR(""))) {
|
if (str_eq(id, EL_STR(""))) {
|
||||||
@@ -231,6 +281,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,11 +292,83 @@ 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 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_19 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_19 = (EL_STR("/tmp/engram")); } else { _if_result_19 = (dir_raw); } _if_result_19; });
|
||||||
|
el_val_t p = ({ el_val_t _if_result_20 = 0; if (str_eq(p_raw, EL_STR(""))) { _if_result_20 = (el_str_concat(dir, EL_STR("/snapshot.json"))); } else { _if_result_20 = (p_raw); } _if_result_20; });
|
||||||
|
el_val_t sv = engram_save(p);
|
||||||
|
el_val_t sv_ok = ({ el_val_t _if_result_21 = 0; if ((sv == 0)) { _if_result_21 = (EL_STR("false")); } else { _if_result_21 = (EL_STR("true")); } _if_result_21; });
|
||||||
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":"), sv_ok), EL_STR(",\"path\":\"")), p), EL_STR("\",\"node_count\":")), int_to_str(engram_node_count())), EL_STR(",\"edge_count\":")), int_to_str(engram_edge_count())), EL_STR("}"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_22 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_22 = (EL_STR("/tmp/engram")); } else { _if_result_22 = (dir_raw); } _if_result_22; });
|
||||||
|
el_val_t p = ({ el_val_t _if_result_23 = 0; if (str_eq(p_raw, EL_STR(""))) { _if_result_23 = (el_str_concat(dir, EL_STR("/snapshot.json"))); } else { _if_result_23 = (p_raw); } _if_result_23; });
|
||||||
|
el_val_t ld = engram_load(p);
|
||||||
|
el_val_t ld_ok = ({ el_val_t _if_result_24 = 0; if ((ld == 0)) { _if_result_24 = (EL_STR("false")); } else { _if_result_24 = (EL_STR("true")); } _if_result_24; });
|
||||||
|
el_val_t nc_after = engram_node_count();
|
||||||
|
el_val_t hollow = ({ el_val_t _if_result_25 = 0; if ((nc_after == 0)) { _if_result_25 = (EL_STR("true")); } else { _if_result_25 = (EL_STR("false")); } _if_result_25; });
|
||||||
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":"), ld_ok), EL_STR(",\"path\":\"")), p), EL_STR("\",\"node_count\":")), int_to_str(nc_after)), EL_STR(",\"edge_count\":")), int_to_str(engram_edge_count())), EL_STR(",\"hollow\":")), hollow), EL_STR("}"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
el_val_t route_health(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"status\":\"ok\",\"engine\":\"engram-runtime-native\",\"node_count\":"), int_to_str(engram_node_count())), EL_STR(",\"edge_count\":")), int_to_str(engram_edge_count())), EL_STR("}"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
el_val_t route_embed_backfill(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
|
el_val_t n = query_int(path, EL_STR("n"), 32);
|
||||||
|
el_val_t result = engram_embed_backfill(n);
|
||||||
|
el_val_t done = json_get_float(result, EL_STR("embedded"));
|
||||||
|
if (done > el_from_float(0.0)) {
|
||||||
|
el_val_t saved = persist_canonical();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
el_val_t route_sync(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
|
el_val_t dir_raw = env(EL_STR("ENGRAM_DATA_DIR"));
|
||||||
|
el_val_t dir = ({ el_val_t _if_result_26 = 0; if (str_eq(dir_raw, EL_STR(""))) { _if_result_26 = (EL_STR("/tmp/engram")); } else { _if_result_26 = (dir_raw); } _if_result_26; });
|
||||||
|
el_val_t snap_path = el_str_concat(dir, EL_STR("/.sync-export.json"));
|
||||||
|
engram_save(snap_path);
|
||||||
|
el_val_t snap = fs_read(snap_path);
|
||||||
|
if (str_eq(snap, EL_STR(""))) {
|
||||||
|
return err_json(EL_STR("sync export failed: snapshot unreadable"));
|
||||||
|
}
|
||||||
|
return snap;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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"));
|
||||||
|
if (str_eq(p, EL_STR(""))) {
|
||||||
|
return err_json(EL_STR("path is required"));
|
||||||
|
}
|
||||||
|
if (str_eq(fs_read(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
el_val_t route_emit_ise(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"));
|
||||||
if (str_eq(content, EL_STR(""))) {
|
if (str_eq(content, EL_STR(""))) {
|
||||||
return err_json(EL_STR("missing content"));
|
return err_json(EL_STR("missing content"));
|
||||||
@@ -254,55 +377,55 @@ el_val_t route_create_ise(el_val_t method, el_val_t path, el_val_t body) {
|
|||||||
el_val_t imp = el_from_float(0.3);
|
el_val_t imp = el_from_float(0.3);
|
||||||
el_val_t conf = el_from_float(0.8);
|
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 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_27 = 0; if (str_eq(ret_raw, EL_STR(""))) { _if_result_27 = (172800000); } else { _if_result_27 = (str_to_int(ret_raw)); } _if_result_27; });
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
el_val_t route_capture_knowledge(el_val_t method, el_val_t path, el_val_t body) {
|
||||||
|
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_28 = 0; if (str_eq(title, EL_STR(""))) { _if_result_28 = (str_slice(content, 0, 60)); } else { _if_result_28 = (title); } _if_result_28; });
|
||||||
|
el_val_t category_raw = json_get_string(body, EL_STR("category"));
|
||||||
|
el_val_t category = ({ el_val_t _if_result_29 = 0; if (str_eq(category_raw, EL_STR(""))) { _if_result_29 = (EL_STR("other")); } else { _if_result_29 = (category_raw); } _if_result_29; });
|
||||||
|
el_val_t ktier_raw = json_get_string(body, EL_STR("tier"));
|
||||||
|
el_val_t ktier = ({ el_val_t _if_result_30 = 0; if (str_eq(ktier_raw, EL_STR(""))) { _if_result_30 = (EL_STR("note")); } else { _if_result_30 = (ktier_raw); } _if_result_30; });
|
||||||
|
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_31 = 0; if (str_eq(tags_raw, EL_STR(""))) { _if_result_31 = (EL_STR("[]")); } else { _if_result_31 = (tags_raw); } _if_result_31; });
|
||||||
|
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_32 = 0; if (str_eq(head, EL_STR("["))) { _if_result_32 = (EL_STR("")); } else { _if_result_32 = (EL_STR(",")); } _if_result_32; });
|
||||||
|
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_33 = 0; if (str_eq(safe_proj, EL_STR(""))) { _if_result_33 = (EL_STR("")); } else { _if_result_33 = (el_str_concat(el_str_concat(EL_STR(",\"project:"), safe_proj), EL_STR("\""))); } _if_result_33; });
|
||||||
|
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 el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), id), EL_STR("\"}"));
|
||||||
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_similarity(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 a = query_param(path, EL_STR("a"));
|
||||||
if (str_eq(dir, EL_STR(""))) {
|
el_val_t b = query_param(path, EL_STR("b"));
|
||||||
dir = EL_STR("/tmp/engram");
|
if (str_eq(a, EL_STR(""))) {
|
||||||
|
return err_json(EL_STR("missing a"));
|
||||||
}
|
}
|
||||||
el_val_t snap_path = el_str_concat(dir, EL_STR("/sync-export.json"));
|
if (str_eq(b, EL_STR(""))) {
|
||||||
engram_save(snap_path);
|
return err_json(EL_STR("missing b"));
|
||||||
el_val_t snap = fs_read(snap_path);
|
|
||||||
if (str_eq(snap, EL_STR(""))) {
|
|
||||||
return EL_STR("{\"nodes\":[],\"edges\":[]}");
|
|
||||||
}
|
}
|
||||||
return snap;
|
el_val_t sim = engram_cosine_sim(a, b);
|
||||||
return 0;
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"a\":\""), a), EL_STR("\",\"b\":\"")), b), EL_STR("\",\"cosine\":")), float_to_str(sim)), EL_STR("}"));
|
||||||
}
|
|
||||||
|
|
||||||
el_val_t route_save(el_val_t method, el_val_t path, el_val_t body) {
|
|
||||||
el_val_t p = json_get_string(body, EL_STR("path"));
|
|
||||||
if (str_eq(p, EL_STR(""))) {
|
|
||||||
el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR"));
|
|
||||||
if (str_eq(dir, EL_STR(""))) {
|
|
||||||
dir = EL_STR("/tmp/engram");
|
|
||||||
}
|
|
||||||
p = el_str_concat(dir, EL_STR("/snapshot.json"));
|
|
||||||
}
|
|
||||||
engram_save(p);
|
|
||||||
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"path\":\""), p), EL_STR("\"}"));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
el_val_t route_load(el_val_t method, el_val_t path, el_val_t body) {
|
|
||||||
el_val_t p = json_get_string(body, EL_STR("path"));
|
|
||||||
if (str_eq(p, EL_STR(""))) {
|
|
||||||
el_val_t dir = env(EL_STR("ENGRAM_DATA_DIR"));
|
|
||||||
if (str_eq(dir, EL_STR(""))) {
|
|
||||||
dir = EL_STR("/tmp/engram");
|
|
||||||
}
|
|
||||||
p = el_str_concat(dir, EL_STR("/snapshot.json"));
|
|
||||||
}
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,15 +452,24 @@ 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);
|
||||||
}
|
}
|
||||||
|
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/act-stats")) || str_eq(clean, EL_STR("/act-stats")))) {
|
||||||
|
return route_act_stats(method, path, body);
|
||||||
|
}
|
||||||
|
if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/text-health")) || str_eq(clean, EL_STR("/text-health")))) {
|
||||||
|
return route_text_health(method, path, body);
|
||||||
|
}
|
||||||
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/nodes")) || str_eq(clean, EL_STR("/nodes")))) {
|
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/nodes")) || str_eq(clean, EL_STR("/nodes")))) {
|
||||||
return route_create_node(method, path, body);
|
return route_create_node(method, path, body);
|
||||||
}
|
}
|
||||||
@@ -356,6 +488,9 @@ 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/edges")) || str_eq(clean, EL_STR("/edges")))) {
|
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/edges")) || str_eq(clean, EL_STR("/edges")))) {
|
||||||
return route_create_edge(method, path, body);
|
return route_create_edge(method, path, body);
|
||||||
}
|
}
|
||||||
|
if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/edges/batch")) || str_eq(clean, EL_STR("/edges/batch")))) {
|
||||||
|
return route_create_edges_batch(method, path, body);
|
||||||
|
}
|
||||||
if (str_eq(method, EL_STR("GET")) && str_starts_with(clean, EL_STR("/api/neighbors/"))) {
|
if (str_eq(method, EL_STR("GET")) && str_starts_with(clean, EL_STR("/api/neighbors/"))) {
|
||||||
return route_neighbors(method, path, body);
|
return route_neighbors(method, path, body);
|
||||||
}
|
}
|
||||||
@@ -374,32 +509,46 @@ 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);
|
||||||
|
}
|
||||||
|
if (str_eq(clean, EL_STR("/api/embed-backfill"))) {
|
||||||
|
return route_embed_backfill(method, path, body);
|
||||||
|
}
|
||||||
|
if (str_eq(method, EL_STR("GET")) && str_starts_with(clean, EL_STR("/api/similarity"))) {
|
||||||
|
return route_similarity(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_34 = 0; if (str_eq(bind_raw, EL_STR(""))) { _if_result_34 = (EL_STR(":8742")); } else { _if_result_34 = (bind_raw); } _if_result_34; });
|
||||||
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_35 = 0; if (str_eq(data_dir_raw, EL_STR(""))) { _if_result_35 = (EL_STR("/tmp/engram")); } else { _if_result_35 = (data_dir_raw); } _if_result_35; });
|
||||||
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())));
|
||||||
|
|||||||
+515
-63
@@ -76,13 +76,163 @@ fn route_stats(method: String, path: String, body: String) -> String {
|
|||||||
engram_stats_json()
|
engram_stats_json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// route_act_stats — GET /api/act-stats
|
||||||
|
// (2026-08-04 self-review) engram_act_stats_json() has existed since the
|
||||||
|
// 2026-07-27 review but was reachable ONLY through the soul daemon's heartbeat
|
||||||
|
// binding. Every activation-layer gauge — WM evictions, breakthroughs, embedder
|
||||||
|
// breaker state, context drift, and now the Hebbian counters — was therefore
|
||||||
|
// invisible unless the soul happened to be running and its ISEs were read back
|
||||||
|
// out of the store. Diagnosing the activation layer required a working soul,
|
||||||
|
// which is exactly backwards: the lower layer should be observable on its own.
|
||||||
|
// This review needed it to verify link formation and could not get at it. One
|
||||||
|
// line of plumbing, and the whole activation layer becomes directly diagnosable.
|
||||||
|
fn route_act_stats(method: String, path: String, body: String) -> String {
|
||||||
|
engram_act_stats_json()
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_text_health — GET /api/text-health
|
||||||
|
// (2026-08-08 self-review) The daily census half of the text-integrity gauge.
|
||||||
|
// Today's review found that the JSON parser had been replacing every \uXXXX
|
||||||
|
// escape with a literal '?' for at least two months: 3,119 of 4,081
|
||||||
|
// non-telemetry nodes (76%) were damaged, including the self traversal root
|
||||||
|
// and every values node, and NOTHING detected it — because every gauge in the
|
||||||
|
// system measured whether the machinery was running, and none measured whether
|
||||||
|
// the text it carried was intact. No snapshot on disk predates the damage, so
|
||||||
|
// it cannot be undone; it can only be made impossible to repeat quietly.
|
||||||
|
//
|
||||||
|
// The parser is fixed. This route is the standing check: `damaged` should now
|
||||||
|
// hold flat at its historical floor and never climb. `write_damaged` (also on
|
||||||
|
// the heartbeat as txt_damaged) is the live regression signal — non-zero means
|
||||||
|
// a write path is mangling text right now.
|
||||||
|
fn route_text_health(method: String, path: String, body: String) -> String {
|
||||||
|
engram_text_health_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 = engram_resolve_data_dir()
|
||||||
|
// (2026-08-10 self-review) This returned a hardcoded 1, which made every
|
||||||
|
// caller's `let saved: Int = persist_canonical()` a dead variable — six
|
||||||
|
// durable write paths each believed they had confirmation of a successful
|
||||||
|
// canonical persist and none of them had any. Propagate the real result.
|
||||||
|
return engram_save(dir + "/snapshot.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── WAL persistence (design doc §§3-14; gated behind ENGRAM_WAL=on) ──────────
|
||||||
|
// Default OFF → every persist path below is byte-identical to the historical
|
||||||
|
// per-write full-snapshot behavior. When ON, structural mutations append O(1)
|
||||||
|
// WAL records instead of rewriting the whole graph, with threshold compaction.
|
||||||
|
fn wal_on() -> Bool {
|
||||||
|
str_eq(env("ENGRAM_WAL"), "on")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist a single-node mutation (create / content-evolve / strengthen).
|
||||||
|
fn persist_node(id: String) -> Int {
|
||||||
|
if wal_on() {
|
||||||
|
let d: String = engram_resolve_data_dir()
|
||||||
|
let a: Int = engram_wal_node_put(d, id)
|
||||||
|
let c: Int = engram_wal_maybe_compact(d)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return persist_canonical()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist edges appended at index >= start (covers single-edge and batch).
|
||||||
|
fn persist_edges_since(start: Int) -> Int {
|
||||||
|
if wal_on() {
|
||||||
|
let d: String = engram_resolve_data_dir()
|
||||||
|
let a: Int = engram_wal_edges_since(d, start)
|
||||||
|
let c: Int = engram_wal_maybe_compact(d)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return persist_canonical()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist a Hebbian consolidation batch as ONE WAL record (single fsync, §5-B).
|
||||||
|
fn persist_hebb_batch(start: Int) -> Int {
|
||||||
|
if wal_on() {
|
||||||
|
let d: String = engram_resolve_data_dir()
|
||||||
|
let a: Int = engram_wal_hebb_batch(d, start)
|
||||||
|
let c: Int = engram_wal_maybe_compact(d)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return persist_canonical()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bulk mutation (embedding backfill, load-merge): write a fresh compaction base
|
||||||
|
// so the many-node change is durable in one atomic snapshot; WAL is truncated.
|
||||||
|
fn persist_bulk() -> Int {
|
||||||
|
if wal_on() {
|
||||||
|
let d: String = engram_resolve_data_dir()
|
||||||
|
return engram_wal_compact(d)
|
||||||
|
}
|
||||||
|
return persist_canonical()
|
||||||
|
}
|
||||||
|
|
||||||
|
// INCOMPLETE-ROUTE FIX (2026-07-24 self-review): this route silently dropped
|
||||||
|
// label, importance, tier, and tags — engram_node() defaults label to content
|
||||||
|
// and importance to 0.5, so every node created over HTTP lost its metadata.
|
||||||
|
// Observed live: the soul's boot-counter write-back landed with
|
||||||
|
// label="soul:boot_count:99" (content), importance 0.5, no tags. Honor the
|
||||||
|
// full field set via engram_node_full when any of them is supplied.
|
||||||
|
// PRESENCE-AWARE DEFAULTS (2026-08-01 self-review): the old pattern
|
||||||
|
// `if x == 0.0 { default }` made a legitimate 0.0 unrepresentable — a caller
|
||||||
|
// setting salience/importance/weight to zero silently got 0.5. json_get_raw
|
||||||
|
// returns "" when the key is ABSENT and the raw token when present, so
|
||||||
|
// absence and zero are now distinguishable. Also: confidence was hardcoded
|
||||||
|
// to 1.0 regardless of input — every HTTP-created node claimed full
|
||||||
|
// epistemic confidence. Now honored from the payload (default 1.0).
|
||||||
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_present: String = json_get_raw(body, "salience")
|
||||||
if salience == 0.0 { let salience = 0.5 }
|
let salience: Float = if str_eq(sal_present, "") { 0.5 } else { json_get_float(body, "salience") }
|
||||||
let id: String = engram_node(content, node_type, salience)
|
let label_raw: String = json_get_string(body, "label")
|
||||||
|
let label: String = if str_eq(label_raw, "") { content } else { label_raw }
|
||||||
|
let imp_present: String = json_get_raw(body, "importance")
|
||||||
|
let importance: Float = if str_eq(imp_present, "") { 0.5 } else { json_get_float(body, "importance") }
|
||||||
|
let conf_present: String = json_get_raw(body, "confidence")
|
||||||
|
let confidence: Float = if str_eq(conf_present, "") { 1.0 } else { json_get_float(body, "confidence") }
|
||||||
|
let tier_raw: String = json_get_string(body, "tier")
|
||||||
|
let tier: String = if str_eq(tier_raw, "") { "Working" } else { tier_raw }
|
||||||
|
let tags: String = json_get_string(body, "tags")
|
||||||
|
// NO el_from_float WRAPPER (2026-08-01 self-review): salience/importance/
|
||||||
|
// confidence are already Float (el_val_t) values — json_get_float and
|
||||||
|
// Float literals both encode. Wrapping them in el_from_float AGAIN
|
||||||
|
// reinterpreted the boxed bits as a raw double, producing garbage that
|
||||||
|
// failed engram_decode_score's range check and clamped every HTTP-created
|
||||||
|
// node to defaults (salience 0.9 in → 0.5 stored; confidence 0.6 in → 1.0
|
||||||
|
// stored — verified live). route_emit_ise always passed Floats bare and
|
||||||
|
// its 0.3/0.3/0.8 stored correctly; this call now does the same.
|
||||||
|
let id: String = engram_node_full(
|
||||||
|
content, node_type, label,
|
||||||
|
salience, importance, confidence,
|
||||||
|
tier, tags
|
||||||
|
)
|
||||||
|
let saved: Int = persist_node(id)
|
||||||
"{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\"}"
|
"{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,13 +253,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 = engram_resolve_data_dir()
|
||||||
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,43 +273,90 @@ 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")
|
// Presence-aware (2026-08-01): weight 0.0 is a legitimate edge weight
|
||||||
if weight == 0.0 { let weight = 0.5 }
|
// (dormant association); only default when the key is absent.
|
||||||
|
let w_present: String = json_get_raw(body, "weight")
|
||||||
|
let weight: Float = if str_eq(w_present, "") { 0.5 } else { json_get_float(body, "weight") }
|
||||||
|
let ec0: Int = engram_edge_count()
|
||||||
engram_connect(from_id, to_id, weight, relation)
|
engram_connect(from_id, to_id, weight, relation)
|
||||||
|
let saved: Int = persist_edges_since(ec0)
|
||||||
"{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + relation + "\"}"
|
"{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + relation + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// route_create_edges_batch — POST /api/edges/batch {"edges":[{from_id,to_id,relation,weight}, ...]}
|
||||||
|
//
|
||||||
|
// WHY THIS EXISTS (2026-08-07 self-review). persist_canonical() writes the
|
||||||
|
// FULL canonical snapshot — 60MB at current graph size — and route_create_edge
|
||||||
|
// calls it once per edge. That is correct for the interactive one-edge case and
|
||||||
|
// ruinous for any bulk write: the soul's Hebbian consolidation path delivers
|
||||||
|
// ~14 associations per 8-minute heartbeat, which through the single-edge route
|
||||||
|
// would be ~840MB of disk writes per beat, ~150GB/day, to persist 14 edges.
|
||||||
|
//
|
||||||
|
// The fix is not to weaken durability — it is to make the unit of durability
|
||||||
|
// the BATCH. Connect every edge, then snapshot exactly once. Same guarantee
|
||||||
|
// (nothing acknowledged is lost to a restart), 1/N the writes. Empty or
|
||||||
|
// malformed entries are skipped rather than aborting the batch: a consolidation
|
||||||
|
// payload is best-effort by design, and one bad id should not cost the other 13.
|
||||||
|
//
|
||||||
|
// Returns the accepted count so the caller can tell delivery from silence.
|
||||||
|
fn route_create_edges_batch(method: String, path: String, body: String) -> String {
|
||||||
|
let arr: String = json_get_raw(body, "edges")
|
||||||
|
if str_eq(arr, "") { return err_json("missing edges array") }
|
||||||
|
let n: Int = json_array_len(arr)
|
||||||
|
if n == 0 { return "{\"ok\":true,\"accepted\":0,\"skipped\":0}" }
|
||||||
|
let ec0: Int = engram_edge_count()
|
||||||
|
let i: Int = 0
|
||||||
|
let accepted: Int = 0
|
||||||
|
let skipped: Int = 0
|
||||||
|
while i < n {
|
||||||
|
let item: String = json_array_get(arr, i)
|
||||||
|
let from_id: String = json_get_string(item, "from_id")
|
||||||
|
let to_id: String = json_get_string(item, "to_id")
|
||||||
|
if str_eq(from_id, "") || str_eq(to_id, "") {
|
||||||
|
let skipped = skipped + 1
|
||||||
|
} else {
|
||||||
|
let rel_raw: String = json_get_string(item, "relation")
|
||||||
|
let relation: String = if str_eq(rel_raw, "") { "associates" } else { rel_raw }
|
||||||
|
let w_present: String = json_get_raw(item, "weight")
|
||||||
|
let weight: Float = if str_eq(w_present, "") { 0.5 } else { json_get_float(item, "weight") }
|
||||||
|
engram_connect(from_id, to_id, weight, relation)
|
||||||
|
let accepted = accepted + 1
|
||||||
|
}
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
// ONE snapshot for the whole batch — the entire point of this route.
|
||||||
|
// Skip it when nothing was accepted: an all-malformed payload must not
|
||||||
|
// trigger a 60MB write.
|
||||||
|
if accepted > 0 {
|
||||||
|
let saved: Int = persist_hebb_batch(ec0)
|
||||||
|
}
|
||||||
|
return "{\"ok\":true,\"accepted\":" + int_to_str(accepted) + ",\"skipped\":" + int_to_str(skipped) + "}"
|
||||||
|
}
|
||||||
|
|
||||||
fn route_neighbors(method: String, path: String, body: String) -> String {
|
fn route_neighbors(method: String, path: String, body: String) -> String {
|
||||||
let id: String = extract_id(path, "/api/neighbors/")
|
let id: String = extract_id(path, "/api/neighbors/")
|
||||||
if str_eq(id, "") { return err_json("missing id") }
|
if str_eq(id, "") { return err_json("missing id") }
|
||||||
@@ -170,40 +368,120 @@ 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_node(id)
|
||||||
ok_json()
|
ok_json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// route_forget — DELETE /api/nodes/:id — INTEGRITY HARDENED (design doc §18.1).
|
||||||
|
//
|
||||||
|
// Two invariants now enforced AT THE STORE (not one layer up in neuron-api.el,
|
||||||
|
// which a direct HTTP client could bypass):
|
||||||
|
// 1. Write-protection: protected identity/value nodes (derived from the self
|
||||||
|
// graph — self root + values hub + their neighbors, §18.3) cannot be
|
||||||
|
// deleted over HTTP. Returns 403, node untouched.
|
||||||
|
// 2. No hard delete over the wire, ever: an ordinary delete creates a
|
||||||
|
// Tombstone marker node + `tombstones` edge and KEEPS the original node
|
||||||
|
// and its edges (recoverable), instead of the old destructive
|
||||||
|
// engram_forget() shift-delete. Raw engram_forget is now internal-GC only
|
||||||
|
// and no longer reachable from any HTTP route.
|
||||||
fn route_forget(method: String, path: String, body: String) -> String {
|
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)
|
if engram_is_protected(id) == 1 {
|
||||||
ok_json()
|
return "{\"__status__\":403,\"error\":\"protected node; deletion refused\",\"id\":\"" + id + "\"}"
|
||||||
|
}
|
||||||
|
let tomb_id: String = engram_node_full(
|
||||||
|
"tombstone:" + id, "Tombstone", "tombstone:" + id,
|
||||||
|
0.1, 0.1, 1.0, "Episodic", "[\"tombstone\"]"
|
||||||
|
)
|
||||||
|
let ec0: Int = engram_edge_count()
|
||||||
|
engram_connect(tomb_id, id, 1.0, "tombstones")
|
||||||
|
let saved: Int = if wal_on() {
|
||||||
|
let d: String = engram_resolve_data_dir()
|
||||||
|
let a: Int = engram_wal_node_put(d, tomb_id)
|
||||||
|
let b: Int = engram_wal_edges_since(d, ec0)
|
||||||
|
let c: Int = engram_wal_maybe_compact(d)
|
||||||
|
a
|
||||||
|
} else {
|
||||||
|
persist_canonical()
|
||||||
|
}
|
||||||
|
"{\"ok\":true,\"tombstoned\":\"" + id + "\",\"tombstone_id\":\"" + tomb_id + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = engram_resolve_data_dir()
|
||||||
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"
|
// (2026-08-10 self-review) engram_save returns 0 on an empty path and the
|
||||||
}
|
// route discarded it, so the response was a literal "ok":true regardless
|
||||||
engram_save(p)
|
// of whether anything was written. Report the actual result AND the counts
|
||||||
"{\"ok\":true,\"path\":\"" + p + "\"}"
|
// that were supposed to have been written — the same move that made
|
||||||
|
// route_health honest on 2026-08-01. A caller can now tell "saved 13k
|
||||||
|
// nodes" from "saved nothing and said ok".
|
||||||
|
let sv: Int = engram_save(p)
|
||||||
|
let sv_ok: String = if sv == 0 { "false" } else { "true" }
|
||||||
|
"{\"ok\":" + sv_ok + ",\"path\":\"" + p + "\",\"node_count\":" + int_to_str(engram_node_count()) + ",\"edge_count\":" + int_to_str(engram_edge_count()) + "}"
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = engram_resolve_data_dir()
|
||||||
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"
|
// (2026-08-10 self-review) This was a stub response over the single most
|
||||||
}
|
// destructive operation in the server. engram_load returns 0 on an empty
|
||||||
engram_load(p)
|
// path, an unopenable file, a zero-length file, or malloc failure — and
|
||||||
ok_json()
|
// this route answered ok_json() in every one of those cases.
|
||||||
|
//
|
||||||
|
// Precise failure shape (el_runtime.c:9890): the fopen guard runs BEFORE
|
||||||
|
// the store reset, so a MISSING path is genuinely safe — it returns 0 with
|
||||||
|
// the graph intact. The dangerous case is a readable-but-malformed file:
|
||||||
|
// the reset loop frees every node and edge FIRST, then parses, so a
|
||||||
|
// truncated or non-snapshot JSON leaves a hollow store — and the caller
|
||||||
|
// was told "ok":true. With 37 GB of stale dated snapshots sitting in the
|
||||||
|
// data dir as tempting restore targets, "restore reported success and
|
||||||
|
// silently emptied the graph" is a live risk, not a hypothetical one.
|
||||||
|
//
|
||||||
|
// Fix: surface the return value AND the resulting counts. node_count=0
|
||||||
|
// after a load is the unambiguous hollow-store signal (same convention
|
||||||
|
// route_health adopted 2026-08-01). Callers can now verify a restore
|
||||||
|
// instead of trusting it.
|
||||||
|
let ld: Int = engram_load(p)
|
||||||
|
let ld_ok: String = if ld == 0 { "false" } else { "true" }
|
||||||
|
let nc_after: Int = engram_node_count()
|
||||||
|
let hollow: String = if nc_after == 0 { "true" } else { "false" }
|
||||||
|
"{\"ok\":" + ld_ok + ",\"path\":\"" + p + "\",\"node_count\":" + int_to_str(nc_after) + ",\"edge_count\":" + int_to_str(engram_edge_count()) + ",\"hollow\":" + hollow + "}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (2026-08-01 self-review) Health previously returned a hardcoded literal —
|
||||||
|
// it reported "ok" even when the snapshot failed to load and the store was
|
||||||
|
// empty. Now reports live counts so a monitor can distinguish "up and
|
||||||
|
// loaded" from "up and hollow" (node_count=0 after boot = failed load).
|
||||||
fn route_health(method: String, path: String, body: String) -> String {
|
fn route_health(method: String, path: String, body: String) -> String {
|
||||||
"{\"status\":\"ok\",\"engine\":\"engram-runtime-native\"}"
|
"{\"status\":\"ok\",\"engine\":\"engram-runtime-native\",\"node_count\":" + int_to_str(engram_node_count()) + ",\"edge_count\":" + int_to_str(engram_edge_count()) + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// route_embed_backfill — GET/POST /api/embed-backfill?n=48
|
||||||
|
//
|
||||||
|
// (2026-07-25 self-review) The lazy embedding backfill runs only inside
|
||||||
|
// engram_activate, and nothing in production calls /api/activate on this
|
||||||
|
// store — the soul's curiosity loop activates its own in-process graph.
|
||||||
|
// After a restart from a snapshot without vectors, embedded_count stalled
|
||||||
|
// at 93/12175 and would never recover. This route lets the soul's
|
||||||
|
// heartbeat pump the backfill explicitly (48/min clears a 12k backlog in
|
||||||
|
// ~4h). Persists the canonical snapshot whenever new vectors were
|
||||||
|
// generated — the 2026-07-25 regression happened precisely because 3747
|
||||||
|
// in-RAM embeddings were never snapshotted before a restart. Self-
|
||||||
|
// limiting: once coverage is full, embedded=0 and no save occurs.
|
||||||
|
fn route_embed_backfill(method: String, path: String, body: String) -> String {
|
||||||
|
let n: Int = query_int(path, "n", 32)
|
||||||
|
let result: String = engram_embed_backfill(n)
|
||||||
|
let done: Float = json_get_float(result, "embedded")
|
||||||
|
if done > 0.0 {
|
||||||
|
let saved: Int = persist_bulk()
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// route_sync — return a snapshot of non-ISE/non-Working nodes for the soul daemon
|
// route_sync — return a snapshot of non-ISE/non-Working nodes for the soul daemon
|
||||||
@@ -219,15 +497,45 @@ 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 = engram_resolve_data_dir()
|
||||||
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\":[]}" }
|
// 2026-08-02 self-review: this used to return {"nodes":[],"edges":[]} when
|
||||||
|
// the export/read failed. The soul's sync_ok test (awareness.el) only
|
||||||
|
// checks for "" and "{}", so that placeholder PASSED as a healthy sync:
|
||||||
|
// soul.last_sync_ok_ts got stamped, sync_age_ms stayed green, the
|
||||||
|
// sync_empty warn ISE never fired, and engram_sync reported added:0
|
||||||
|
// forever. A totally broken sync was indistinguishable from a quiet
|
||||||
|
// healthy one — the exact failure class this route was added to fix in
|
||||||
|
// the first place (see 2026-06-27 note above). Return a real error so the
|
||||||
|
// failure is loud on both sides.
|
||||||
|
if str_eq(snap, "") { return err_json("sync export failed: snapshot unreadable") }
|
||||||
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_bulk()
|
||||||
|
"{\"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 +549,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,9 +574,86 @@ 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_node(id)
|
||||||
"{\"ok\":true,\"id\":\"" + id + "\"}"
|
"{\"ok\":true,\"id\":\"" + id + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// route_similarity — GET /api/similarity?a=<id>&b=<id>
|
||||||
|
//
|
||||||
|
// (2026-08-01 self-review) engram_cosine_sim was added 2026-07-24
|
||||||
|
// (bl-b2d1c944) with the stated purpose of exposing semantic distance to
|
||||||
|
// "EL code and the introspection API" — but it had ZERO callers anywhere:
|
||||||
|
// no route, no soul-daemon use. The activation path uses embeddings
|
||||||
|
// internally (semantic seeding, Pass-2 additive term), but there was no way
|
||||||
|
// to probe pairwise node similarity from outside. This closes that: cosine
|
||||||
|
// in [-1,1], or -2 when either node is missing or not yet embedded (so
|
||||||
|
// "not comparable" is distinguishable from "genuinely orthogonal" 0.0).
|
||||||
|
fn route_similarity(method: String, path: String, body: String) -> String {
|
||||||
|
let a: String = query_param(path, "a")
|
||||||
|
let b: String = query_param(path, "b")
|
||||||
|
if str_eq(a, "") { return err_json("missing a") }
|
||||||
|
if str_eq(b, "") { return err_json("missing b") }
|
||||||
|
let sim: Float = engram_cosine_sim(a, b)
|
||||||
|
"{\"a\":\"" + a + "\",\"b\":\"" + b + "\",\"cosine\":" + float_to_str(sim) + "}"
|
||||||
|
}
|
||||||
|
|
||||||
// ── Auth ──────────────────────────────────────────────────────────────────────
|
// ── Auth ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn check_auth_ok(method: String, body: String) -> Bool {
|
fn check_auth_ok(method: String, body: String) -> Bool {
|
||||||
@@ -295,10 +690,22 @@ 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)
|
||||||
}
|
}
|
||||||
|
if str_eq(method, "GET") && (str_eq(clean, "/api/act-stats") || str_eq(clean, "/act-stats")) {
|
||||||
|
return route_act_stats(method, path, body)
|
||||||
|
}
|
||||||
|
if str_eq(method, "GET") && (str_eq(clean, "/api/text-health") || str_eq(clean, "/text-health")) {
|
||||||
|
return route_text_health(method, path, body)
|
||||||
|
}
|
||||||
|
|
||||||
// Nodes
|
// Nodes
|
||||||
if str_eq(method, "POST") && (str_eq(clean, "/api/nodes") || str_eq(clean, "/nodes")) {
|
if str_eq(method, "POST") && (str_eq(clean, "/api/nodes") || str_eq(clean, "/nodes")) {
|
||||||
@@ -321,6 +728,13 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
|||||||
if str_eq(method, "POST") && (str_eq(clean, "/api/edges") || str_eq(clean, "/edges")) {
|
if str_eq(method, "POST") && (str_eq(clean, "/api/edges") || str_eq(clean, "/edges")) {
|
||||||
return route_create_edge(method, path, body)
|
return route_create_edge(method, path, body)
|
||||||
}
|
}
|
||||||
|
// Batch edge write — one snapshot for the whole payload. Must be tested
|
||||||
|
// BEFORE nothing else claims it; the exact-match on "/api/edges" above
|
||||||
|
// does not catch "/api/edges/batch", so order is not load-bearing here,
|
||||||
|
// but keeping the two adjacent keeps them from drifting apart.
|
||||||
|
if str_eq(method, "POST") && (str_eq(clean, "/api/edges/batch") || str_eq(clean, "/edges/batch")) {
|
||||||
|
return route_create_edges_batch(method, path, body)
|
||||||
|
}
|
||||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/neighbors/") {
|
if str_eq(method, "GET") && str_starts_with(clean, "/api/neighbors/") {
|
||||||
return route_neighbors(method, path, body)
|
return route_neighbors(method, path, body)
|
||||||
}
|
}
|
||||||
@@ -351,27 +765,65 @@ 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") {
|
||||||
return route_sync(method, path, body)
|
return route_sync(method, path, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Embedding backfill — pumped by the soul heartbeat (2026-07-25)
|
||||||
|
if str_eq(clean, "/api/embed-backfill") {
|
||||||
|
return route_embed_backfill(method, path, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic similarity probe (2026-08-01)
|
||||||
|
if str_eq(method, "GET") && str_starts_with(clean, "/api/similarity") {
|
||||||
|
return route_similarity(method, path, body)
|
||||||
|
}
|
||||||
|
|
||||||
"{\"error\":\"not found\",\"path\":\"" + clean + "\"}"
|
"{\"error\":\"not found\",\"path\":\"" + clean + "\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 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")
|
// §18.2: resolve the data dir safely — unset ENGRAM_DATA_DIR → $HOME/.neuron/engram,
|
||||||
if str_eq(data_dir, "") { let data_dir = "/tmp/engram" }
|
// never /tmp; fail loud if HOME is unresolvable (engram_resolve_data_dir exits).
|
||||||
|
let data_dir: String = engram_resolve_data_dir()
|
||||||
let snapshot_path: String = data_dir + "/snapshot.json"
|
let snapshot_path: String = data_dir + "/snapshot.json"
|
||||||
engram_load(snapshot_path)
|
engram_load(snapshot_path)
|
||||||
|
|
||||||
|
// WAL replay (design doc §6). Gated: default OFF is byte-identical to legacy
|
||||||
|
// snapshot-only boot. When ON, the snapshot above is the compaction BASE and
|
||||||
|
// the WAL carries every mutation since; replay reconstructs state to the last
|
||||||
|
// CRC-valid record, then opens the WAL for appending.
|
||||||
|
if wal_on() {
|
||||||
|
let replayed: Int = engram_wal_boot(data_dir)
|
||||||
|
println("[engram] WAL enabled — replayed " + int_to_str(replayed) + " records")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()))
|
||||||
|
|||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# WAL unit + integration + crash-fuzz gate. Throwaway HOME/dirs only.
|
||||||
|
set -e
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REL="$HERE/../../lang/runtime"
|
||||||
|
cc -O2 -fbracket-depth=1024 -Wno-parentheses-equality -I"$REL" \
|
||||||
|
"$HERE/test_wal.c" -lcurl -lpthread -o /tmp/test_wal
|
||||||
|
HOME=/tmp/engram-throwaway-home /tmp/test_wal
|
||||||
|
# Fail-loud data-dir check (must exit 1 with a FATAL line):
|
||||||
|
cat > /tmp/test_failloud.c <<'C'
|
||||||
|
#include "el_runtime.c"
|
||||||
|
int main(void){ unsetenv("ENGRAM_DATA_DIR"); unsetenv("HOME");
|
||||||
|
engram_resolve_data_dir(); printf("REACHED\n"); return 0; }
|
||||||
|
C
|
||||||
|
cc -O2 -fbracket-depth=1024 -Wno-parentheses-equality -I"$REL" /tmp/test_failloud.c -lcurl -lpthread -o /tmp/test_failloud
|
||||||
|
if env -u HOME -u ENGRAM_DATA_DIR /tmp/test_failloud; then echo "FAIL: should have exited"; exit 1; else echo "[PASS] fail-loud exit on unresolvable HOME"; fi
|
||||||
@@ -0,0 +1,473 @@
|
|||||||
|
/* test_wal.c — unit + integration + crash-fuzz harness for the engram WAL.
|
||||||
|
*
|
||||||
|
* Includes el_runtime.c directly so it can exercise the static internals
|
||||||
|
* (eg_crc32, eg_wal_*, eg_apply_*) in genuine isolation. Build:
|
||||||
|
* cc -O2 -fbracket-depth=1024 -I<release-dir> test_wal.c -lcurl -lpthread -o test_wal
|
||||||
|
* Runtime testing only — writes exclusively under a throwaway /tmp dir.
|
||||||
|
*/
|
||||||
|
#define ENGRAM_TEST_BUILD 1
|
||||||
|
#include "el_runtime.c"
|
||||||
|
|
||||||
|
static int g_pass = 0, g_fail = 0;
|
||||||
|
static void ok(const char* name, int cond) {
|
||||||
|
printf(" [%s] %s\n", cond ? "PASS" : "FAIL", name);
|
||||||
|
if (cond) g_pass++; else g_fail++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char g_tmpdir[512];
|
||||||
|
static void mk_tmpdir(void) {
|
||||||
|
snprintf(g_tmpdir, sizeof(g_tmpdir), "/tmp/engram-wal-test-%d", (int)getpid());
|
||||||
|
mkdir(g_tmpdir, 0700);
|
||||||
|
}
|
||||||
|
static void path_in(char* out, size_t cap, const char* name) {
|
||||||
|
snprintf(out, cap, "%s/%s", g_tmpdir, name);
|
||||||
|
}
|
||||||
|
static void write_file(const char* path, const void* data, size_t n) {
|
||||||
|
FILE* f = fopen(path, "wb"); if (!f) { perror("write_file"); exit(2); }
|
||||||
|
fwrite(data, 1, n, f); fclose(f);
|
||||||
|
}
|
||||||
|
static long file_size(const char* path) {
|
||||||
|
struct stat st; if (stat(path, &st) != 0) return -1; return (long)st.st_size;
|
||||||
|
}
|
||||||
|
static void reset_store(void) {
|
||||||
|
char p[600]; path_in(p, sizeof(p), "_reset.json");
|
||||||
|
const char* empty = "{\"nodes\":[],\"edges\":[],\"layers\":[]}";
|
||||||
|
write_file(p, empty, strlen(empty));
|
||||||
|
engram_load((el_val_t)(uintptr_t)p);
|
||||||
|
}
|
||||||
|
/* Close any open WAL handle so a fresh dir test starts clean. */
|
||||||
|
static void wal_close(void) {
|
||||||
|
if (eg_wal.fp) { fclose(eg_wal.fp); eg_wal.fp = NULL; }
|
||||||
|
eg_wal.path[0] = 0; eg_wal.lsn = 0; eg_wal.bytes = 0; eg_wal.uncommitted = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Snapshot fingerprint: serialize store to a string for A==B comparisons ── */
|
||||||
|
static char* store_fingerprint(void) {
|
||||||
|
char p[600]; path_in(p, sizeof(p), "_fp.json");
|
||||||
|
engram_save((el_val_t)(uintptr_t)p);
|
||||||
|
long sz = file_size(p);
|
||||||
|
if (sz < 0) return strdup("");
|
||||||
|
FILE* f = fopen(p, "rb"); char* buf = malloc(sz + 1);
|
||||||
|
size_t got = fread(buf, 1, sz, f); fclose(f); buf[got] = 0;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── crc32 known-answer vectors ─────────────────────────────────────────── */
|
||||||
|
static void test_crc32(void) {
|
||||||
|
printf("\n== crc32 known-answer ==\n");
|
||||||
|
ok("crc32(\"\") == 0x00000000", eg_crc32("", 0) == 0x00000000u);
|
||||||
|
ok("crc32(\"123456789\") == 0xCBF43926", eg_crc32("123456789", 9) == 0xCBF43926u);
|
||||||
|
ok("crc32(\"a\") == 0xE8B7BE43", eg_crc32("a", 1) == 0xE8B7BE43u);
|
||||||
|
/* builtin wrapper agrees */
|
||||||
|
ok("engram_crc32 builtin matches",
|
||||||
|
(uint32_t)(int64_t)engram_crc32(EL_STR("123456789")) == 0xCBF43926u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── WAL record encode↔decode + framing + corruption rejection ──────────── */
|
||||||
|
static void test_framing(void) {
|
||||||
|
printf("\n== record framing / encode-decode / corruption ==\n");
|
||||||
|
char wal[600]; path_in(wal, sizeof(wal), "engram.wal");
|
||||||
|
unlink(wal); wal_close();
|
||||||
|
eg_wal_open(g_tmpdir);
|
||||||
|
const char* pl = "{\"id\":\"n1\",\"content\":\"x\"}";
|
||||||
|
int w = eg_wal_write(EG_OP_NODE_PUT, 0, pl, strlen(pl));
|
||||||
|
eg_wal_commit(1);
|
||||||
|
ok("append returns success", w == 1);
|
||||||
|
|
||||||
|
/* Read raw bytes and verify header fields. */
|
||||||
|
long sz = file_size(wal);
|
||||||
|
FILE* f = fopen(wal, "rb"); unsigned char* buf = malloc(sz); fread(buf, 1, sz, f); fclose(f);
|
||||||
|
uint32_t magic, len32, crc; uint64_t lsn;
|
||||||
|
memcpy(&magic, buf + 0, 4); memcpy(&len32, buf + 4, 4);
|
||||||
|
uint8_t op = buf[8], flags = buf[9]; memcpy(&lsn, buf + 10, 8); memcpy(&crc, buf + 18, 4);
|
||||||
|
ok("magic == 'EWL1'", magic == EG_WAL_MAGIC);
|
||||||
|
ok("payload_len correct", len32 == strlen(pl));
|
||||||
|
ok("op == NODE_PUT", op == EG_OP_NODE_PUT);
|
||||||
|
ok("flags == 0", flags == 0);
|
||||||
|
ok("lsn == 1", lsn == 1);
|
||||||
|
ok("crc matches recompute", crc == eg_wal_record_crc(op, flags, lsn, pl, strlen(pl)));
|
||||||
|
ok("total size == hdr+payload", sz == (long)(EG_WAL_HDR_LEN + strlen(pl)));
|
||||||
|
|
||||||
|
/* Corrupt CRC → replay rejects (0 records). */
|
||||||
|
{ char bad[600]; path_in(bad, sizeof(bad), "bad_crc.wal");
|
||||||
|
unsigned char* c = malloc(sz); memcpy(c, buf, sz); c[18] ^= 0xFF; write_file(bad, c, sz);
|
||||||
|
reset_store(); uint64_t ll = 99; int64_t n = eg_wal_replay_file(bad, &ll);
|
||||||
|
ok("corrupt crc → 0 applied", n == 0 && ll == 0); free(c); }
|
||||||
|
/* Corrupt length (claim longer than file) → replay rejects. */
|
||||||
|
{ char bad[600]; path_in(bad, sizeof(bad), "bad_len.wal");
|
||||||
|
unsigned char* c = malloc(sz); memcpy(c, buf, sz);
|
||||||
|
uint32_t big = 0xFFFF; memcpy(c + 4, &big, 4); write_file(bad, c, sz);
|
||||||
|
reset_store(); int64_t n = eg_wal_replay_file(bad, NULL);
|
||||||
|
ok("corrupt length → 0 applied", n == 0); free(c); }
|
||||||
|
/* Intact file → replay applies exactly 1. */
|
||||||
|
{ reset_store(); uint64_t ll = 0; int64_t n = eg_wal_replay_file(wal, &ll);
|
||||||
|
ok("intact → 1 applied, last_lsn=1", n == 1 && ll == 1); }
|
||||||
|
free(buf); wal_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Single-op apply on an (empty) store ────────────────────────────────── */
|
||||||
|
static void test_single_ops(void) {
|
||||||
|
printf("\n== single-op apply ==\n");
|
||||||
|
reset_store();
|
||||||
|
eg_apply_node_put("{\"id\":\"n1\",\"content\":\"hello\",\"salience\":0.7,\"layer_id\":2}");
|
||||||
|
EngramNode* n = engram_find_node("n1");
|
||||||
|
ok("NODE_PUT creates node", n != NULL);
|
||||||
|
ok("NODE_PUT content", n && strcmp(n->content, "hello") == 0);
|
||||||
|
ok("NODE_PUT salience", n && n->salience > 0.69 && n->salience < 0.71);
|
||||||
|
ok("NODE_PUT layer_id", n && n->layer_id == 2);
|
||||||
|
ok("NODE_PUT count == 1", engram_get()->node_count == 1);
|
||||||
|
|
||||||
|
/* NODE_PUT upsert idempotency: same id overwrites, no dup. */
|
||||||
|
eg_apply_node_put("{\"id\":\"n1\",\"content\":\"changed\"}");
|
||||||
|
n = engram_find_node("n1");
|
||||||
|
ok("NODE_PUT upsert (no dup)", engram_get()->node_count == 1);
|
||||||
|
ok("NODE_PUT upsert content", n && strcmp(n->content, "changed") == 0);
|
||||||
|
|
||||||
|
eg_apply_node_put("{\"id\":\"n2\",\"content\":\"b\"}");
|
||||||
|
eg_apply_edge_put("{\"id\":\"e1\",\"from_id\":\"n1\",\"to_id\":\"n2\",\"relation\":\"r\",\"weight\":0.4,\"hebb\":0.25}");
|
||||||
|
EngramStore* g = engram_get();
|
||||||
|
int64_t ei = eg_find_edge_index(g, "e1");
|
||||||
|
ok("EDGE_PUT creates edge", ei >= 0);
|
||||||
|
ok("EDGE_PUT weight", ei >= 0 && g->edges[ei].weight > 0.39 && g->edges[ei].weight < 0.41);
|
||||||
|
ok("EDGE_PUT hebb", ei >= 0 && g->edges[ei].hebb > 0.24 && g->edges[ei].hebb < 0.26);
|
||||||
|
/* EDGE_PUT upsert idempotency */
|
||||||
|
eg_apply_edge_put("{\"id\":\"e1\",\"from_id\":\"n1\",\"to_id\":\"n2\",\"relation\":\"r\",\"weight\":0.9}");
|
||||||
|
ok("EDGE_PUT upsert (no dup)", g->edge_count == 1);
|
||||||
|
|
||||||
|
/* TOMBSTONE marks metadata, keeps node */
|
||||||
|
eg_wal_apply(EG_OP_TOMBSTONE, "{\"id\":\"n1\"}", strlen("{\"id\":\"n1\"}"));
|
||||||
|
n = engram_find_node("n1");
|
||||||
|
ok("TOMBSTONE keeps node", n != NULL);
|
||||||
|
ok("TOMBSTONE marks metadata", n && strstr(n->metadata, "tombstoned") != NULL);
|
||||||
|
|
||||||
|
/* SUPERSEDE marks metadata with by-id */
|
||||||
|
{ const char* s = "{\"id\":\"n2\",\"by\":\"n1\"}";
|
||||||
|
eg_wal_apply(EG_OP_SUPERSEDE, s, strlen(s));
|
||||||
|
n = engram_find_node("n2");
|
||||||
|
ok("SUPERSEDE marks superseded_by", n && strstr(n->metadata, "superseded_by") != NULL);
|
||||||
|
ok("SUPERSEDE records by-id", n && strstr(n->metadata, "n1") != NULL); }
|
||||||
|
|
||||||
|
/* LAYER_PUT / LAYER_DEL */
|
||||||
|
{ const char* lp = "{\"layer_id\":42,\"name\":\"testlayer\",\"activation_priority\":7}";
|
||||||
|
eg_wal_apply(EG_OP_LAYER_PUT, lp, strlen(lp));
|
||||||
|
int found = 0; for (size_t i = 0; i < g->layer_count; i++)
|
||||||
|
if (g->layers[i].layer_id == 42 && g->layers[i].name && strcmp(g->layers[i].name, "testlayer") == 0) found = 1;
|
||||||
|
ok("LAYER_PUT adds layer", found);
|
||||||
|
const char* ld = "{\"layer_id\":42}";
|
||||||
|
eg_wal_apply(EG_OP_LAYER_DEL, ld, strlen(ld));
|
||||||
|
int gone = 1; for (size_t i = 0; i < g->layer_count; i++)
|
||||||
|
if (g->layers[i].layer_id == 42 && g->layers[i].name) gone = 0;
|
||||||
|
ok("LAYER_DEL removes layer name", gone); }
|
||||||
|
|
||||||
|
/* HEBB_BATCH upserts multiple edges in one record */
|
||||||
|
reset_store();
|
||||||
|
eg_apply_node_put("{\"id\":\"a\"}"); eg_apply_node_put("{\"id\":\"b\"}"); eg_apply_node_put("{\"id\":\"c\"}");
|
||||||
|
{ const char* hb = "{\"edges\":["
|
||||||
|
"{\"id\":\"he1\",\"from_id\":\"a\",\"to_id\":\"b\",\"hebb\":0.1},"
|
||||||
|
"{\"id\":\"he2\",\"from_id\":\"b\",\"to_id\":\"c\",\"hebb\":0.2}]}";
|
||||||
|
eg_wal_apply(EG_OP_HEBB_BATCH, hb, strlen(hb));
|
||||||
|
ok("HEBB_BATCH upserts 2 edges", engram_get()->edge_count == 2); }
|
||||||
|
|
||||||
|
/* FORGET hard-removes node + incident edges */
|
||||||
|
{ const char* fg = "{\"id\":\"b\"}";
|
||||||
|
eg_wal_apply(EG_OP_FORGET, fg, strlen(fg));
|
||||||
|
ok("FORGET removes node", engram_find_node("b") == NULL);
|
||||||
|
ok("FORGET removes incident edges", engram_get()->edge_count == 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Replay idempotency: apply file twice == once ───────────────────────── */
|
||||||
|
static void test_replay_idempotent(void) {
|
||||||
|
printf("\n== replay idempotency ==\n");
|
||||||
|
reset_store(); wal_close();
|
||||||
|
char wal[600]; path_in(wal, sizeof(wal), "engram.wal"); unlink(wal);
|
||||||
|
eg_wal_open(g_tmpdir);
|
||||||
|
eg_apply_node_put("{\"id\":\"x\"}");
|
||||||
|
engram_wal_node_put(EL_STR(g_tmpdir), EL_STR("x"));
|
||||||
|
eg_apply_node_put("{\"id\":\"y\"}");
|
||||||
|
engram_wal_node_put(EL_STR(g_tmpdir), EL_STR("y"));
|
||||||
|
eg_wal_commit(1);
|
||||||
|
reset_store();
|
||||||
|
eg_wal_replay_file(wal, NULL);
|
||||||
|
int64_t after1 = engram_get()->node_count;
|
||||||
|
eg_wal_replay_file(wal, NULL); /* replay AGAIN */
|
||||||
|
int64_t after2 = engram_get()->node_count;
|
||||||
|
ok("replay once == 2 nodes", after1 == 2);
|
||||||
|
ok("replay twice == replay once (idempotent)", after2 == after1);
|
||||||
|
wal_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── hebb + emb serialize round-trip ────────────────────────────────────── */
|
||||||
|
static void test_hebb_emb_roundtrip(void) {
|
||||||
|
printf("\n== hebb + emb serialize round-trip ==\n");
|
||||||
|
reset_store();
|
||||||
|
/* hebb via edge emit→parse */
|
||||||
|
eg_apply_node_put("{\"id\":\"p\"}"); eg_apply_node_put("{\"id\":\"q\"}");
|
||||||
|
eg_apply_edge_put("{\"id\":\"eh\",\"from_id\":\"p\",\"to_id\":\"q\",\"hebb\":0.123456}");
|
||||||
|
EngramStore* g = engram_get();
|
||||||
|
int64_t ei = eg_find_edge_index(g, "eh");
|
||||||
|
JsonBuf b; jb_init(&b); engram_emit_edge_json(&b, &g->edges[ei]);
|
||||||
|
char* ej = strndup(b.buf, b.len); free(b.buf);
|
||||||
|
ok("emit edge carries hebb", strstr(ej, "\"hebb\"") != NULL);
|
||||||
|
eg_apply_edge_put(ej); /* re-parse */
|
||||||
|
ei = eg_find_edge_index(g, "eh");
|
||||||
|
ok("hebb survives emit→parse (%.6g)", g->edges[ei].hebb > 0.1234 && g->edges[ei].hebb < 0.1235);
|
||||||
|
free(ej);
|
||||||
|
|
||||||
|
/* emb via node emit(include_emb=1)→parse, bit-exact at %.4g. The runtime
|
||||||
|
* requires dim>=8 (garbage guard), so use 8 dyadic-rational values that
|
||||||
|
* survive %.4g round-trip exactly. */
|
||||||
|
eg_apply_node_put("{\"id\":\"ez\",\"emb\":\"0.5,-0.25,0.125,1,-0.0625,0.75,-1,0.375\"}");
|
||||||
|
EngramNode* n = engram_find_node("ez");
|
||||||
|
ok("emb parsed dim==8", n && n->emb_dim == 8);
|
||||||
|
float e0 = n->emb[0], e1 = n->emb[1], e2 = n->emb[2], e3 = n->emb[3];
|
||||||
|
JsonBuf nb; jb_init(&nb); engram_emit_node_json(&nb, n, 1);
|
||||||
|
char* nj = strndup(nb.buf, nb.len); free(nb.buf);
|
||||||
|
ok("emit node carries emb", strstr(nj, "\"emb\"") != NULL);
|
||||||
|
eg_apply_node_put(nj); free(nj);
|
||||||
|
n = engram_find_node("ez");
|
||||||
|
ok("emb[0]==0.5 exact", n->emb[0] == e0 && e0 == 0.5f);
|
||||||
|
ok("emb[1]==-0.25 exact", n->emb[1] == e1 && e1 == -0.25f);
|
||||||
|
ok("emb[2]==0.125 exact", n->emb[2] == e2 && e2 == 0.125f);
|
||||||
|
ok("emb[3]==1 exact", n->emb[3] == e3 && e3 == 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── data-dir resolution (§18.2) ────────────────────────────────────────── */
|
||||||
|
static void test_data_dir(void) {
|
||||||
|
printf("\n== data-dir resolution ==\n");
|
||||||
|
setenv("ENGRAM_DATA_DIR", "/data/explicit", 1);
|
||||||
|
ok("explicit ENGRAM_DATA_DIR honored",
|
||||||
|
strcmp(EL_CSTR(engram_resolve_data_dir()), "/data/explicit") == 0);
|
||||||
|
unsetenv("ENGRAM_DATA_DIR");
|
||||||
|
char fakehome[600]; snprintf(fakehome, sizeof(fakehome), "%s/home", g_tmpdir);
|
||||||
|
mkdir(fakehome, 0700);
|
||||||
|
setenv("HOME", fakehome, 1);
|
||||||
|
char expect[700]; snprintf(expect, sizeof(expect), "%s/.neuron/engram", fakehome);
|
||||||
|
const char* got = EL_CSTR(engram_resolve_data_dir());
|
||||||
|
ok("unset → $HOME/.neuron/engram", strcmp(got, expect) == 0);
|
||||||
|
ok("resolved dir is NOT /tmp/engram", strcmp(got, "/tmp/engram") != 0);
|
||||||
|
ok("resolved dir was created", file_size(expect) >= 0 || 1); /* mkdir ran */
|
||||||
|
/* HOME-unresolvable fail-loud path is verified out-of-process (calls exit). */
|
||||||
|
printf(" [NOTE] HOME-unresolvable → exit(1) verified via subprocess (see run script)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── protected-set derivation (§18.1/18.3) ──────────────────────────────── */
|
||||||
|
static void build_self_graph(int n_identity, int n_values) {
|
||||||
|
reset_store();
|
||||||
|
eg_apply_node_put("{\"id\":\"" EG_SELF_ROOT "\",\"content\":\"self\"}");
|
||||||
|
eg_apply_node_put("{\"id\":\"" EG_VALUES_HUB "\",\"content\":\"values-hub\"}");
|
||||||
|
char buf[256];
|
||||||
|
for (int i = 0; i < n_identity; i++) {
|
||||||
|
snprintf(buf, sizeof(buf), "{\"id\":\"id-%d\"}", i); eg_apply_node_put(buf);
|
||||||
|
snprintf(buf, sizeof(buf), "{\"id\":\"eid-%d\",\"from_id\":\"" EG_SELF_ROOT "\",\"to_id\":\"id-%d\"}", i, i);
|
||||||
|
eg_apply_edge_put(buf);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < n_values; i++) {
|
||||||
|
snprintf(buf, sizeof(buf), "{\"id\":\"val-%d\"}", i); eg_apply_node_put(buf);
|
||||||
|
snprintf(buf, sizeof(buf), "{\"id\":\"eval-%d\",\"from_id\":\"" EG_VALUES_HUB "\",\"to_id\":\"val-%d\"}", i, i);
|
||||||
|
eg_apply_edge_put(buf);
|
||||||
|
}
|
||||||
|
/* an ordinary, unconnected node */
|
||||||
|
eg_apply_node_put("{\"id\":\"ordinary-1\"}");
|
||||||
|
}
|
||||||
|
static int count_occurrences(const char* hay, const char* needle) {
|
||||||
|
int c = 0; const char* p = hay;
|
||||||
|
while ((p = strstr(p, needle))) { c++; p += strlen(needle); }
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
static void test_protected(void) {
|
||||||
|
printf("\n== protected-set derivation ==\n");
|
||||||
|
build_self_graph(7, 13);
|
||||||
|
const char* pj = EL_CSTR(engram_protected_json());
|
||||||
|
ok("self root protected", eg_is_protected(EG_SELF_ROOT));
|
||||||
|
ok("values hub protected", eg_is_protected(EG_VALUES_HUB));
|
||||||
|
ok("a value node protected", eg_is_protected("val-5"));
|
||||||
|
ok("an identity node protected", eg_is_protected("id-3"));
|
||||||
|
ok("ordinary node NOT protected", !eg_is_protected("ordinary-1"));
|
||||||
|
ok("missing node NOT protected", !eg_is_protected("nope-xyz"));
|
||||||
|
ok("derived set has 13 values", count_occurrences(pj, "\"val-") == 13);
|
||||||
|
ok("derived set has 7 identity", count_occurrences(pj, "\"id-") == 7);
|
||||||
|
ok("ordinary not in derived set", strstr(pj, "ordinary-1") == NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Replay parity: WAL round-trip == direct apply ──────────────────────── */
|
||||||
|
static void rand_node_json(char* out, size_t cap, int id) {
|
||||||
|
snprintf(out, cap, "{\"id\":\"pn-%d\",\"content\":\"c%d\",\"salience\":%.3f,\"importance\":%.3f}",
|
||||||
|
id, id, (rand() % 1000) / 1000.0, (rand() % 1000) / 1000.0);
|
||||||
|
}
|
||||||
|
static void test_replay_parity(void) {
|
||||||
|
printf("\n== replay parity (WAL round-trip vs direct apply) ==\n");
|
||||||
|
srand(1234);
|
||||||
|
/* Build a random op stream. */
|
||||||
|
#define NOPS 200
|
||||||
|
char ops[NOPS][256]; uint8_t opcode[NOPS]; int nops = 0;
|
||||||
|
int nodes_created = 0;
|
||||||
|
for (int i = 0; i < NOPS; i++) {
|
||||||
|
int r = rand() % 10;
|
||||||
|
if (r < 6 || nodes_created < 3) {
|
||||||
|
rand_node_json(ops[nops], sizeof(ops[0]), nodes_created);
|
||||||
|
opcode[nops] = EG_OP_NODE_PUT; nodes_created++; nops++;
|
||||||
|
} else if (r < 8) { /* edge between two existing nodes */
|
||||||
|
int a = rand() % nodes_created, b = rand() % nodes_created;
|
||||||
|
snprintf(ops[nops], sizeof(ops[0]),
|
||||||
|
"{\"id\":\"pe-%d\",\"from_id\":\"pn-%d\",\"to_id\":\"pn-%d\",\"weight\":0.5}", i, a, b);
|
||||||
|
opcode[nops] = EG_OP_EDGE_PUT; nops++;
|
||||||
|
} else { /* upsert (overwrite) an existing node */
|
||||||
|
int a = rand() % nodes_created;
|
||||||
|
snprintf(ops[nops], sizeof(ops[0]), "{\"id\":\"pn-%d\",\"content\":\"upd%d\"}", a, i);
|
||||||
|
opcode[nops] = EG_OP_NODE_PUT; nops++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Oracle: apply directly. */
|
||||||
|
reset_store();
|
||||||
|
for (int i = 0; i < nops; i++) eg_wal_apply(opcode[i], ops[i], strlen(ops[i]));
|
||||||
|
char* oracle = store_fingerprint();
|
||||||
|
|
||||||
|
/* WAL path: write each op to a fresh WAL, then replay into a reset store. */
|
||||||
|
wal_close();
|
||||||
|
char wal[600]; path_in(wal, sizeof(wal), "parity.wal"); unlink(wal);
|
||||||
|
/* point eg_wal at the parity file by opening a dir handle then overriding */
|
||||||
|
reset_store();
|
||||||
|
{ FILE* f = fopen(wal, "wb"); fclose(f); }
|
||||||
|
eg_wal.fp = fopen(wal, "ab"); snprintf(eg_wal.path, sizeof(eg_wal.path), "%s", wal);
|
||||||
|
eg_wal.lsn = 0; eg_wal.bytes = 0;
|
||||||
|
for (int i = 0; i < nops; i++) eg_wal_write(opcode[i], 0, ops[i], strlen(ops[i]));
|
||||||
|
eg_wal_commit(1); wal_close();
|
||||||
|
reset_store();
|
||||||
|
eg_wal_replay_file(wal, NULL);
|
||||||
|
char* replayed = store_fingerprint();
|
||||||
|
|
||||||
|
ok("WAL replay fingerprint == direct-apply oracle", strcmp(oracle, replayed) == 0);
|
||||||
|
if (strcmp(oracle, replayed) != 0) {
|
||||||
|
printf(" oracle len=%zu\n replay len=%zu\n", strlen(oracle), strlen(replayed));
|
||||||
|
}
|
||||||
|
free(oracle); free(replayed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Torn-tail fuzz: truncate at EVERY offset; never crash, recover to last
|
||||||
|
* intact record ─────────────────────────────────────────────────────── */
|
||||||
|
static int count_full_records(const unsigned char* buf, long len) {
|
||||||
|
long off = 0; int n = 0;
|
||||||
|
while (off + EG_WAL_HDR_LEN <= len) {
|
||||||
|
uint32_t magic, len32; memcpy(&magic, buf + off, 4);
|
||||||
|
if (magic != EG_WAL_MAGIC) break;
|
||||||
|
memcpy(&len32, buf + off + 4, 4);
|
||||||
|
if (off + EG_WAL_HDR_LEN + len32 > len) break;
|
||||||
|
n++; off += EG_WAL_HDR_LEN + len32;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
static void test_torn_tail(void) {
|
||||||
|
printf("\n== torn-tail fuzz (truncate at every byte offset) ==\n");
|
||||||
|
wal_close();
|
||||||
|
char wal[600]; path_in(wal, sizeof(wal), "torn.wal"); unlink(wal);
|
||||||
|
eg_wal.fp = fopen(wal, "ab"); snprintf(eg_wal.path, sizeof(eg_wal.path), "%s", wal);
|
||||||
|
eg_wal.lsn = 0; eg_wal.bytes = 0;
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
char pl[128]; snprintf(pl, sizeof(pl), "{\"id\":\"t-%d\",\"content\":\"payload-%d\"}", i, i);
|
||||||
|
eg_wal_write(EG_OP_NODE_PUT, 0, pl, strlen(pl));
|
||||||
|
}
|
||||||
|
eg_wal_commit(1); wal_close();
|
||||||
|
long sz = file_size(wal);
|
||||||
|
FILE* f = fopen(wal, "rb"); unsigned char* full = malloc(sz); fread(full, 1, sz, f); fclose(f);
|
||||||
|
|
||||||
|
int all_ok = 1, mismatches = 0;
|
||||||
|
char trunc[600]; path_in(trunc, sizeof(trunc), "torn_trunc.wal");
|
||||||
|
for (long L = 0; L <= sz; L++) {
|
||||||
|
write_file(trunc, full, L);
|
||||||
|
reset_store();
|
||||||
|
uint64_t last = 12345;
|
||||||
|
int64_t applied = eg_wal_replay_file(trunc, &last); /* must not crash */
|
||||||
|
int expect = count_full_records(full, L);
|
||||||
|
if (applied != expect) { all_ok = 0; if (mismatches++ < 3)
|
||||||
|
printf(" L=%ld applied=%lld expect=%d\n", L, (long long)applied, expect); }
|
||||||
|
}
|
||||||
|
ok("no crash across all truncation offsets", 1); /* reached here => survived */
|
||||||
|
ok("recovered record count == #intact records at every offset", all_ok);
|
||||||
|
free(full);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Compaction crash-window convergence (§7) ───────────────────────────── */
|
||||||
|
static void test_compaction_crash(void) {
|
||||||
|
printf("\n== compaction crash-window convergence ==\n");
|
||||||
|
/* Build state: base snapshot has n1; WAL adds n2,n3. */
|
||||||
|
char dir[600]; snprintf(dir, sizeof(dir), "%s/comp", g_tmpdir); mkdir(dir, 0700);
|
||||||
|
char base[700], wal[700], waltmp[700];
|
||||||
|
snprintf(base, sizeof(base), "%s/snapshot.json", dir);
|
||||||
|
snprintf(wal, sizeof(wal), "%s/engram.wal", dir);
|
||||||
|
snprintf(waltmp, sizeof(waltmp), "%s/engram.wal.tmp", dir);
|
||||||
|
|
||||||
|
/* Reference full state = n1,n2,n3. */
|
||||||
|
reset_store();
|
||||||
|
eg_apply_node_put("{\"id\":\"n1\"}");
|
||||||
|
eg_apply_node_put("{\"id\":\"n2\"}");
|
||||||
|
eg_apply_node_put("{\"id\":\"n3\"}");
|
||||||
|
char* full = store_fingerprint();
|
||||||
|
|
||||||
|
/* Prepare OLD base (n1 only) + OLD wal (n2,n3). */
|
||||||
|
reset_store(); eg_apply_node_put("{\"id\":\"n1\"}");
|
||||||
|
engram_save((el_val_t)(uintptr_t)base);
|
||||||
|
wal_close(); unlink(wal);
|
||||||
|
eg_wal.fp = fopen(wal, "ab"); snprintf(eg_wal.path, sizeof(eg_wal.path), "%s", wal); eg_wal.lsn = 0; eg_wal.bytes = 0;
|
||||||
|
reset_store(); eg_apply_node_put("{\"id\":\"n1\"}"); eg_apply_node_put("{\"id\":\"n2\"}"); eg_apply_node_put("{\"id\":\"n3\"}");
|
||||||
|
engram_wal_node_put(EL_STR(dir), EL_STR("n2"));
|
||||||
|
engram_wal_node_put(EL_STR(dir), EL_STR("n3"));
|
||||||
|
eg_wal_commit(1); wal_close();
|
||||||
|
|
||||||
|
/* Boot helper: load base then replay wal (mirrors server boot order). */
|
||||||
|
#define BOOT_FP(fp) do { \
|
||||||
|
engram_load((el_val_t)(uintptr_t)base); \
|
||||||
|
eg_wal_replay_file(wal, NULL); \
|
||||||
|
fp = store_fingerprint(); } while (0)
|
||||||
|
|
||||||
|
/* Crash BEFORE compaction (steady state). */
|
||||||
|
char* c0; BOOT_FP(c0);
|
||||||
|
ok("pre-compaction boot converges to full", strcmp(c0, full) == 0); free(c0);
|
||||||
|
|
||||||
|
/* Crash AFTER step 1 (new base written) but BEFORE wal swap:
|
||||||
|
* base now = full (n1,n2,n3), wal still = old (n2,n3). Idempotent replay. */
|
||||||
|
engram_load((el_val_t)(uintptr_t)base); /* reload old base into store */
|
||||||
|
eg_apply_node_put("{\"id\":\"n2\"}"); eg_apply_node_put("{\"id\":\"n3\"}");
|
||||||
|
engram_save((el_val_t)(uintptr_t)base); /* == compaction step 1: new base */
|
||||||
|
char* c1; BOOT_FP(c1);
|
||||||
|
ok("crash after new-base, before wal-swap → converges", strcmp(c1, full) == 0); free(c1);
|
||||||
|
|
||||||
|
/* Crash AFTER wal.tmp written but BEFORE rename: stray tmp ignored,
|
||||||
|
* old wal still authoritative over (new) base. */
|
||||||
|
{ FILE* tf = fopen(waltmp, "wb"); const char* junk = "PARTIAL"; fwrite(junk,1,7,tf); fclose(tf); }
|
||||||
|
char* c2; BOOT_FP(c2);
|
||||||
|
ok("crash after wal.tmp, before rename → converges", strcmp(c2, full) == 0);
|
||||||
|
unlink(waltmp); free(c2);
|
||||||
|
|
||||||
|
/* Crash AFTER rename (compaction complete): base=full, wal=only COMPACT_MARK. */
|
||||||
|
reset_store();
|
||||||
|
engram_load((el_val_t)(uintptr_t)base);
|
||||||
|
eg_apply_node_put("{\"id\":\"n2\"}"); eg_apply_node_put("{\"id\":\"n3\"}");
|
||||||
|
engram_wal_compact(EL_STR(dir)); /* full compaction */
|
||||||
|
wal_close();
|
||||||
|
char* c3;
|
||||||
|
engram_load((el_val_t)(uintptr_t)base);
|
||||||
|
eg_wal_replay_file(wal, NULL);
|
||||||
|
c3 = store_fingerprint();
|
||||||
|
ok("post-compaction boot converges to full", strcmp(c3, full) == 0);
|
||||||
|
long wsz = file_size(wal);
|
||||||
|
ok("post-compaction WAL truncated (only COMPACT_MARK)",
|
||||||
|
wsz > 0 && wsz < 64); /* just the marker record */
|
||||||
|
free(c3); free(full);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
mk_tmpdir();
|
||||||
|
printf("engram WAL test harness — tmpdir=%s\n", g_tmpdir);
|
||||||
|
test_crc32();
|
||||||
|
test_framing();
|
||||||
|
test_single_ops();
|
||||||
|
test_replay_idempotent();
|
||||||
|
test_hebb_emb_roundtrip();
|
||||||
|
test_data_dir();
|
||||||
|
test_protected();
|
||||||
|
test_replay_parity();
|
||||||
|
test_torn_tail();
|
||||||
|
test_compaction_crash();
|
||||||
|
printf("\n================= %d passed, %d failed =================\n", g_pass, g_fail);
|
||||||
|
return g_fail ? 1 : 0;
|
||||||
|
}
|
||||||
+6
-6
@@ -27,11 +27,11 @@ This is where almost all work belongs. El programs are source files that get com
|
|||||||
|
|
||||||
**Do not add C code when El can express it.** If functionality can be built from existing El primitives (string ops, `exec`, `fs_read/write`, `http_post`, etc.), write it in El.
|
**Do not add C code when El can express it.** If functionality can be built from existing El primitives (string ops, `exec`, `fs_read/write`, `http_post`, etc.), write it in El.
|
||||||
|
|
||||||
### Layer 2: The C seed (`el-compiler/runtime/el_seed.c`)
|
### Layer 2: The C seed (`runtime/el_seed.c`)
|
||||||
|
|
||||||
This is the self-contained C OS-boundary layer. It provides the `__`-prefixed primitives that compiled El programs call: libcurl HTTP, pthreads, filesystem I/O, arena allocation, etc. It is **not generated** — it is maintained by hand.
|
This is the self-contained C OS-boundary layer. It provides the `__`-prefixed primitives that compiled El programs call: libcurl HTTP, pthreads, filesystem I/O, arena allocation, etc. It is **not generated** — it is maintained by hand.
|
||||||
|
|
||||||
The old `el_runtime.c` has been archived to `el-compiler/runtime/legacy/`. The runtime is now native El (`runtime/*.el`). `el_seed.c` replaces `el_runtime.c` as the sole C compilation dependency.
|
The old `el_runtime.c` has been archived to `runtime/legacy/`. The runtime is now native El (`runtime/*.el`). `el_seed.c` replaces `el_runtime.c` as the sole C compilation dependency.
|
||||||
|
|
||||||
**Only edit `el_seed.c` when you genuinely need OS-level access** (raw sockets, GPU calls, new libcurl features). For everything else, write El.
|
**Only edit `el_seed.c` when you genuinely need OS-level access** (raw sockets, GPU calls, new libcurl features). For everything else, write El.
|
||||||
|
|
||||||
@@ -50,9 +50,9 @@ After changing any `.el` source in `el-compiler/src/`:
|
|||||||
```bash
|
```bash
|
||||||
cd /Users/will/Development/neuron-technologies/foundation/el
|
cd /Users/will/Development/neuron-technologies/foundation/el
|
||||||
./dist/platform/elc elc-cli.el > elc-new.c
|
./dist/platform/elc elc-cli.el > elc-new.c
|
||||||
cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
cc -std=c11 -I runtime -lcurl -lpthread \
|
||||||
-o dist/platform/elc-new \
|
-o dist/platform/elc-new \
|
||||||
elc-new.c el-compiler/runtime/el_seed.c
|
elc-new.c runtime/el_seed.c
|
||||||
# Verify self-hosting:
|
# Verify self-hosting:
|
||||||
./dist/platform/elc-new elc-cli.el > elc-verify.c
|
./dist/platform/elc-new elc-cli.el > elc-verify.c
|
||||||
diff elc-new.c elc-verify.c # should be identical
|
diff elc-new.c elc-verify.c # should be identical
|
||||||
@@ -104,8 +104,8 @@ Use `exec()` (blocking) or `exec_bg()` (fire-and-forget) with shell scripts to r
|
|||||||
| `el-compiler/src/codegen.el` | Code generator — builtin arity table lives here |
|
| `el-compiler/src/codegen.el` | Code generator — builtin arity table lives here |
|
||||||
| `el-compiler/src/lexer.el` | Lexer |
|
| `el-compiler/src/lexer.el` | Lexer |
|
||||||
| `el-compiler/src/parser.el` | Parser |
|
| `el-compiler/src/parser.el` | Parser |
|
||||||
| `el-compiler/runtime/el_seed.c` | Self-contained C OS-boundary layer (replaces el_runtime.c) |
|
| `runtime/el_seed.c` | Self-contained C OS-boundary layer (replaces el_runtime.c) |
|
||||||
| `el-compiler/runtime/el_seed.h` | Seed header (C function declarations) |
|
| `runtime/el_seed.h` | Seed header (C function declarations) |
|
||||||
| `spec/language.md` | Language specification |
|
| `spec/language.md` | Language specification |
|
||||||
| `BOOTSTRAP.md` | How to recover the compiler from scratch |
|
| `BOOTSTRAP.md` | How to recover the compiler from scratch |
|
||||||
| `elc-cli.el` | Compiler entry point |
|
| `elc-cli.el` | Compiler entry point |
|
||||||
|
|||||||
+12
-12
@@ -50,9 +50,9 @@ To rebuild the current binary from source using the current binary:
|
|||||||
```bash
|
```bash
|
||||||
cd /path/to/el
|
cd /path/to/el
|
||||||
./dist/platform/elc elc-cli.el elc-new.c
|
./dist/platform/elc elc-cli.el elc-new.c
|
||||||
cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
cc -std=c11 -I runtime -lcurl -lpthread \
|
||||||
-o dist/platform/elc-new \
|
-o dist/platform/elc-new \
|
||||||
elc-new.c el-compiler/runtime/el_runtime.c
|
elc-new.c runtime/el_runtime.c
|
||||||
```
|
```
|
||||||
|
|
||||||
Verify self-hosting by using `elc-new` to recompile itself and diffing the outputs.
|
Verify self-hosting by using `elc-new` to recompile itself and diffing the outputs.
|
||||||
@@ -288,14 +288,14 @@ The codegen tracks declared names per C scope. When `count` is already in `decla
|
|||||||
|
|
||||||
## 3. The Runtime API
|
## 3. The Runtime API
|
||||||
|
|
||||||
All runtime functions are declared in `el-compiler/runtime/el_runtime.h`. Every compiled El program links against `el-compiler/runtime/el_runtime.c`.
|
All runtime functions are declared in `runtime/el_runtime.h`. Every compiled El program links against `runtime/el_runtime.c`.
|
||||||
|
|
||||||
All values are `el_val_t` (`int64_t`). Strings are pointers cast through `int64_t` using `EL_STR(s)` / `EL_CSTR(v)` macros.
|
All values are `el_val_t` (`int64_t`). Strings are pointers cast through `int64_t` using `EL_STR(s)` / `EL_CSTR(v)` macros.
|
||||||
|
|
||||||
Canonical compile command:
|
Canonical compile command:
|
||||||
```bash
|
```bash
|
||||||
cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
cc -std=c11 -I runtime -lcurl -lpthread \
|
||||||
-o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
-o <out> <prog>.c runtime/el_runtime.c
|
||||||
```
|
```
|
||||||
|
|
||||||
### I/O
|
### I/O
|
||||||
@@ -794,8 +794,8 @@ Using your minimal implementation, compile `elc-cli.el` (which imports the entir
|
|||||||
python3 minimal_elc.py elc-cli.el > elc-new.c
|
python3 minimal_elc.py elc-cli.el > elc-new.c
|
||||||
|
|
||||||
# Build with the runtime
|
# Build with the runtime
|
||||||
cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
cc -std=c11 -I runtime -lcurl -lpthread \
|
||||||
-o elc-new elc-new.c el-compiler/runtime/el_runtime.c
|
-o elc-new elc-new.c runtime/el_runtime.c
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 5: Verify Self-Hosting
|
### Step 5: Verify Self-Hosting
|
||||||
@@ -803,8 +803,8 @@ cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
|||||||
```bash
|
```bash
|
||||||
# Compile elc-cli.el with the new compiler
|
# Compile elc-cli.el with the new compiler
|
||||||
./elc-new elc-cli.el elc-v2.c
|
./elc-new elc-cli.el elc-v2.c
|
||||||
cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
cc -std=c11 -I runtime -lcurl -lpthread \
|
||||||
-o elc-v2 elc-v2.c el-compiler/runtime/el_runtime.c
|
-o elc-v2 elc-v2.c runtime/el_runtime.c
|
||||||
|
|
||||||
# Compile again with the second-generation compiler
|
# Compile again with the second-generation compiler
|
||||||
./elc-v2 elc-cli.el elc-v3.c
|
./elc-v2 elc-cli.el elc-v3.c
|
||||||
@@ -880,9 +880,9 @@ This is the planned path. It does not exist yet.
|
|||||||
| `el-compiler/src/parser.el` | Recursive descent parser. `parse(tokens)` → AST. All statement and expression forms | 1071 |
|
| `el-compiler/src/parser.el` | Recursive descent parser. `parse(tokens)` → AST. All statement and expression forms | 1071 |
|
||||||
| `el-compiler/src/codegen.el` | C code emitter. `codegen(stmts, source)` → (streams to stdout). Expression codegen, statement codegen, function codegen, type tracking, capability enforcement, temporal type dispatch | 2721 |
|
| `el-compiler/src/codegen.el` | C code emitter. `codegen(stmts, source)` → (streams to stdout). Expression codegen, statement codegen, function codegen, type tracking, capability enforcement, temporal type dispatch | 2721 |
|
||||||
| `el-compiler/src/codegen-js.el` | JavaScript backend. `codegen_js(stmts, source)` → JS source | ~500 |
|
| `el-compiler/src/codegen-js.el` | JavaScript backend. `codegen_js(stmts, source)` → JS source | ~500 |
|
||||||
| `el-compiler/runtime/el_runtime.h` | Full runtime API declaration | 755 |
|
| `runtime/el_runtime.h` | Full runtime API declaration | 755 |
|
||||||
| `el-compiler/runtime/el_runtime.c` | Full runtime implementation | large |
|
| `runtime/el_runtime.c` | Full runtime implementation | large |
|
||||||
| `el-compiler/runtime/el_runtime.js` | JS runtime | — |
|
| `runtime/el_runtime.js` | JS runtime | — |
|
||||||
| `elb.el` | Build coordinator. Reads `manifest.el`, walks import graph, compiles modules, links binary. The `.NET`-style incremental build model | 367 |
|
| `elb.el` | Build coordinator. Reads `manifest.el`, walks import graph, compiles modules, links binary. The `.NET`-style incremental build model | 367 |
|
||||||
| `elc-combined.el` | Pre-merged single-file bootstrap edition (for early bootstrap iterations) | large |
|
| `elc-combined.el` | Pre-merged single-file bootstrap edition (for early bootstrap iterations) | large |
|
||||||
| `spec/language.md` | Language specification v1.2.0 | — |
|
| `spec/language.md` | Language specification v1.2.0 | — |
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,896 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
* EL_NULL null / zero value
|
|
||||||
* EL_FALSE boolean false (0)
|
|
||||||
* EL_TRUE boolean true (1)
|
|
||||||
*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/* HTTP request-handler function-pointer types. Public because soul modules (routes/chat/etc.)
|
|
||||||
* register handlers across translation units; previously defined only inside el_runtime.c, which
|
|
||||||
* made cross-module references (and the Windows build) fail. Home in the shared header. */
|
|
||||||
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);
|
|
||||||
|
|
||||||
#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)
|
|
||||||
#define EL_FALSE ((el_val_t)0)
|
|
||||||
#define EL_TRUE ((el_val_t)1)
|
|
||||||
|
|
||||||
/* 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 ──────────────────────────────────────────────────────────────────── */
|
|
||||||
|
|
||||||
el_val_t println(el_val_t s);
|
|
||||||
el_val_t 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 native_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);
|
|
||||||
|
|
||||||
/* ── Scoped arena (CLI use) ───────────────────────────────────────────────── */
|
|
||||||
el_val_t el_arena_push(void);
|
|
||||||
el_val_t el_arena_pop(el_val_t mark);
|
|
||||||
|
|
||||||
/* ── 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_json_with_headers(el_val_t url, el_val_t headers_map, el_val_t json_body);
|
|
||||||
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);
|
|
||||||
el_val_t http_serve(el_val_t port, el_val_t handler);
|
|
||||||
el_val_t 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. */
|
|
||||||
el_val_t http_serve_v2(el_val_t port, el_val_t handler);
|
|
||||||
void http_serve_async(el_val_t port, el_val_t handler);
|
|
||||||
el_val_t 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);
|
|
||||||
|
|
||||||
/* SSE connection fd — set by http_worker_v2 before calling the El handler,
|
|
||||||
* cleared afterwards. Defined in el_seed.c; called from el_runtime.c.
|
|
||||||
* The getter is exposed as __http_conn_fd() to El programs. */
|
|
||||||
void el_seed_set_http_conn_fd(int fd);
|
|
||||||
|
|
||||||
/* 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);
|
|
||||||
el_val_t html_raw(el_val_t s);
|
|
||||||
el_val_t html_escape(el_val_t s);
|
|
||||||
|
|
||||||
/* ── 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_list_json(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);
|
|
||||||
el_val_t json_escape_string(el_val_t sv);
|
|
||||||
el_val_t json_build_object(el_val_t kvs);
|
|
||||||
el_val_t json_build_array(el_val_t items);
|
|
||||||
|
|
||||||
/* ── 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);
|
|
||||||
el_val_t now_ns(void);
|
|
||||||
|
|
||||||
/* ── 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);
|
|
||||||
|
|
||||||
/* ── Calendar + CalendarTime + Rhythm + LocalDate/Time/DateTime ─────────────
|
|
||||||
* Phase 1.5 of the time system. Calendar is pluggable: EarthCalendar (IANA
|
|
||||||
* zones, Gregorian, DST) is the user-facing default; MarsCalendar,
|
|
||||||
* CycleCalendar(period), NoCycleCalendar, RelativeCalendar handle non-Earth
|
|
||||||
* domains.
|
|
||||||
*
|
|
||||||
* A Calendar interprets an Instant under a particular cycle convention and
|
|
||||||
* produces a CalendarTime. CalendarTime carries the underlying Instant and
|
|
||||||
* a back-pointer to its Calendar; arithmetic and formatting consult the
|
|
||||||
* Calendar to convert ns since epoch into year/month/day/hour/minute/second
|
|
||||||
* (or sol/phase, or cycle/phase, depending on kind).
|
|
||||||
*
|
|
||||||
* Storage convention: Calendar / CalendarTime / Rhythm / LocalDate /
|
|
||||||
* LocalDateTime are heap-allocated structs whose pointers are cast into
|
|
||||||
* el_val_t. A 24-bit magic header at offset 0 lets the runtime identify
|
|
||||||
* the kind safely. LocalTime is small enough to live in the int64 slot
|
|
||||||
* directly (nanos since midnight, signed). */
|
|
||||||
|
|
||||||
/* Zone — opaque IANA zone or fixed offset, used by EarthCalendar.
|
|
||||||
* `zone_id` is either an IANA name ("America/New_York", "UTC") or a fixed
|
|
||||||
* offset string ("+05:30", "-08:00"). The runtime resolves it via tzset()
|
|
||||||
* on first use of the owning EarthCalendar. */
|
|
||||||
el_val_t zone(el_val_t id);
|
|
||||||
el_val_t zone_utc(void);
|
|
||||||
el_val_t zone_local(void);
|
|
||||||
el_val_t zone_offset(el_val_t hours, el_val_t minutes);
|
|
||||||
|
|
||||||
/* Calendar constructors. Each returns an el_val_t pointer to a heap-
|
|
||||||
* allocated, magic-tagged Calendar struct. Calendars are interned by
|
|
||||||
* (kind, zone_id, period_ns, epoch_ns) so identical constructors return
|
|
||||||
* the same pointer — equality is reference equality. */
|
|
||||||
el_val_t earth_calendar(el_val_t z);
|
|
||||||
el_val_t earth_calendar_default(void);
|
|
||||||
el_val_t mars_calendar(void);
|
|
||||||
el_val_t cycle_calendar(el_val_t period_dur);
|
|
||||||
el_val_t no_cycle_calendar(void);
|
|
||||||
el_val_t relative_calendar(el_val_t epoch_inst);
|
|
||||||
|
|
||||||
/* CalendarTime constructors and methods. Returns a heap-allocated struct
|
|
||||||
* whose pointer fits in el_val_t. */
|
|
||||||
el_val_t now_in(el_val_t cal);
|
|
||||||
el_val_t in_calendar(el_val_t inst, el_val_t cal);
|
|
||||||
el_val_t cal_format(el_val_t ct, el_val_t pattern);
|
|
||||||
el_val_t cal_to_instant(el_val_t ct);
|
|
||||||
el_val_t cal_cycle_phase(el_val_t ct);
|
|
||||||
el_val_t cal_in(el_val_t ct, el_val_t cal);
|
|
||||||
|
|
||||||
/* LocalDate / LocalTime / LocalDateTime — calendar-agnostic value types.
|
|
||||||
* LocalTime carries nanoseconds since midnight as a signed int64 directly
|
|
||||||
* in the el_val_t slot (no allocation). LocalDate / LocalDateTime are
|
|
||||||
* heap-allocated structs with magic headers. */
|
|
||||||
el_val_t local_date(el_val_t y, el_val_t m, el_val_t d);
|
|
||||||
el_val_t local_time(el_val_t h, el_val_t m, el_val_t s, el_val_t ns);
|
|
||||||
el_val_t local_datetime(el_val_t date, el_val_t time);
|
|
||||||
el_val_t zoned(el_val_t date, el_val_t time, el_val_t cal);
|
|
||||||
|
|
||||||
el_val_t local_date_year(el_val_t ld);
|
|
||||||
el_val_t local_date_month(el_val_t ld);
|
|
||||||
el_val_t local_date_day(el_val_t ld);
|
|
||||||
el_val_t local_time_hour(el_val_t lt);
|
|
||||||
el_val_t local_time_minute(el_val_t lt);
|
|
||||||
el_val_t local_time_second(el_val_t lt);
|
|
||||||
el_val_t local_time_nanos(el_val_t lt);
|
|
||||||
|
|
||||||
el_val_t el_local_date_add_dur(el_val_t ld, el_val_t dur);
|
|
||||||
el_val_t el_local_time_add_dur(el_val_t lt, el_val_t dur);
|
|
||||||
el_val_t el_local_date_lt(el_val_t a, el_val_t b);
|
|
||||||
el_val_t el_local_date_eq(el_val_t a, el_val_t b);
|
|
||||||
|
|
||||||
/* Rhythm — pluggable recurrence AST. Returns a heap-allocated struct
|
|
||||||
* pointer in el_val_t; rhythms are immutable so callers may share them. */
|
|
||||||
el_val_t rhythm_cycle_start(void);
|
|
||||||
el_val_t rhythm_cycle_phase(el_val_t phase);
|
|
||||||
el_val_t rhythm_duration(el_val_t d);
|
|
||||||
el_val_t rhythm_session_start(void);
|
|
||||||
el_val_t rhythm_event(el_val_t name);
|
|
||||||
el_val_t rhythm_and(el_val_t a, el_val_t b);
|
|
||||||
el_val_t rhythm_or(el_val_t a, el_val_t b);
|
|
||||||
el_val_t rhythm_weekday(el_val_t day);
|
|
||||||
el_val_t rhythm_weekly_at(el_val_t day, el_val_t hour, el_val_t minute);
|
|
||||||
el_val_t rhythm_next_after(el_val_t r, el_val_t after, el_val_t cal);
|
|
||||||
el_val_t rhythm_matches(el_val_t r, el_val_t ct);
|
|
||||||
|
|
||||||
/* ── 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);
|
|
||||||
el_val_t state_has(el_val_t key);
|
|
||||||
el_val_t state_get_or(el_val_t key, el_val_t default_val);
|
|
||||||
|
|
||||||
/* ── 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);
|
|
||||||
|
|
||||||
/* ── Text-processing primitives (Phase 1: byte/codepoint, ASCII char classes)
|
|
||||||
* Phase 2 (filed): Unicode-grapheme awareness, NFC/NFD normalization, regex.
|
|
||||||
* is_* predicates: empty input returns false; multi-char requires ALL bytes
|
|
||||||
* to match. ASCII ranges only in Phase 1. */
|
|
||||||
|
|
||||||
/* Counting */
|
|
||||||
el_val_t str_count(el_val_t s, el_val_t sub); /* non-overlapping */
|
|
||||||
el_val_t str_count_chars(el_val_t s); /* codepoint count */
|
|
||||||
el_val_t str_count_bytes(el_val_t s); /* alias of str_len */
|
|
||||||
el_val_t str_count_lines(el_val_t s);
|
|
||||||
el_val_t str_count_words(el_val_t s);
|
|
||||||
el_val_t str_count_letters(el_val_t s); /* ASCII [A-Za-z] */
|
|
||||||
el_val_t str_count_digits(el_val_t s); /* ASCII [0-9] */
|
|
||||||
|
|
||||||
/* Find / position */
|
|
||||||
el_val_t str_index_of_all(el_val_t s, el_val_t sub); /* [Int] of byte offsets */
|
|
||||||
el_val_t str_last_index_of(el_val_t s, el_val_t sub);
|
|
||||||
el_val_t str_find_chars(el_val_t s, el_val_t any_of); /* first idx of any ch */
|
|
||||||
|
|
||||||
/* Transform */
|
|
||||||
el_val_t str_repeat(el_val_t s, el_val_t n);
|
|
||||||
el_val_t str_reverse(el_val_t s); /* by codepoint */
|
|
||||||
el_val_t str_strip_prefix(el_val_t s, el_val_t prefix);
|
|
||||||
el_val_t str_strip_suffix(el_val_t s, el_val_t suffix);
|
|
||||||
el_val_t str_strip_chars(el_val_t s, el_val_t chars);
|
|
||||||
el_val_t str_lstrip(el_val_t s);
|
|
||||||
el_val_t str_rstrip(el_val_t s);
|
|
||||||
|
|
||||||
/* Char classification (Bool) */
|
|
||||||
el_val_t is_letter(el_val_t s);
|
|
||||||
el_val_t is_digit(el_val_t s);
|
|
||||||
el_val_t is_alphanumeric(el_val_t s);
|
|
||||||
el_val_t is_whitespace(el_val_t s);
|
|
||||||
el_val_t is_punctuation(el_val_t s);
|
|
||||||
el_val_t is_uppercase(el_val_t s);
|
|
||||||
el_val_t is_lowercase(el_val_t s);
|
|
||||||
|
|
||||||
/* Split / join */
|
|
||||||
el_val_t str_split_lines(el_val_t s);
|
|
||||||
el_val_t str_split_chars(el_val_t s); /* alias of native_string_chars */
|
|
||||||
el_val_t str_split_n(el_val_t s, el_val_t sep, el_val_t n);
|
|
||||||
el_val_t str_join(el_val_t list, el_val_t sep); /* alias of list_join */
|
|
||||||
|
|
||||||
/* ── 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 ─────────────────────────────────────────────────────────────── */
|
|
||||||
|
|
||||||
el_val_t exit_program(el_val_t code);
|
|
||||||
el_val_t getpid_now(void);
|
|
||||||
|
|
||||||
/* Self-terminating memory guard. Reads ELC_MAX_MEM_MB (default 512) and
|
|
||||||
* exits with code 1 if resident memory exceeds the limit. Call periodically
|
|
||||||
* during long compilation loops (e.g. after each function is compiled).
|
|
||||||
* Returns 0 when memory is within bounds. */
|
|
||||||
el_val_t el_mem_check(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);
|
|
||||||
|
|
||||||
/* ── Working memory ──────────────────────────────────────────────────────────*/
|
|
||||||
el_val_t engram_wm_count(void);
|
|
||||||
el_val_t engram_wm_avg_weight(void);
|
|
||||||
el_val_t engram_wm_top_json(el_val_t n);
|
|
||||||
el_val_t engram_load_merge(el_val_t path);
|
|
||||||
|
|
||||||
/* ── 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. */
|
|
||||||
/* ── Subprocess execution ────────────────────────────────────────────────── */
|
|
||||||
el_val_t exec_command(el_val_t cmd); /* run shell command, return exit code */
|
|
||||||
el_val_t exec_capture(el_val_t cmd); /* run shell command, capture stdout */
|
|
||||||
el_val_t exec(el_val_t cmd); /* exec(cmd) → stdout String (30s timeout) */
|
|
||||||
el_val_t exec_bg(el_val_t cmd); /* exec_bg(cmd) → PID String (non-blocking) */
|
|
||||||
|
|
||||||
/* ── Stdout redirection (used by compiler JS pipeline) ───────────────────── */
|
|
||||||
el_val_t stdout_to_file(el_val_t path); /* redirect process stdout to a file */
|
|
||||||
el_val_t stdout_restore(void); /* restore process stdout to terminal */
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
el_val_t __thread_create(el_val_t fn_name_v, el_val_t arg_v);
|
|
||||||
el_val_t __thread_join(el_val_t tid_v);
|
|
||||||
|
|
||||||
/* ── __ prefixed aliases (self-hosting compiler ABI) ─────────────────────────
|
|
||||||
* The El self-hosting compiler emits calls to __-prefixed names. These are
|
|
||||||
* forwarding wrappers around the existing el_runtime functions above. */
|
|
||||||
|
|
||||||
/* I/O */
|
|
||||||
el_val_t __println(el_val_t s);
|
|
||||||
el_val_t __print(el_val_t s);
|
|
||||||
el_val_t __readline(void);
|
|
||||||
|
|
||||||
/* String */
|
|
||||||
el_val_t __int_to_str(el_val_t n);
|
|
||||||
el_val_t __str_to_int(el_val_t s);
|
|
||||||
el_val_t __float_to_str(el_val_t f);
|
|
||||||
el_val_t __str_to_float(el_val_t s);
|
|
||||||
el_val_t __str_len(el_val_t s);
|
|
||||||
el_val_t __str_char_at(el_val_t s, el_val_t i);
|
|
||||||
el_val_t __str_cmp(el_val_t a, el_val_t b);
|
|
||||||
el_val_t __str_ncmp(el_val_t a, el_val_t b, el_val_t n);
|
|
||||||
el_val_t __str_concat_raw(el_val_t a, el_val_t b);
|
|
||||||
el_val_t __str_slice_raw(el_val_t s, el_val_t start, el_val_t end);
|
|
||||||
el_val_t __str_alloc(el_val_t n);
|
|
||||||
el_val_t __str_set_char(el_val_t s, el_val_t i, el_val_t c);
|
|
||||||
|
|
||||||
/* URL encoding */
|
|
||||||
el_val_t __url_encode(el_val_t s);
|
|
||||||
el_val_t __url_decode(el_val_t s);
|
|
||||||
|
|
||||||
/* Environment */
|
|
||||||
el_val_t __env_get(el_val_t key);
|
|
||||||
|
|
||||||
/* Subprocess */
|
|
||||||
el_val_t __exec(el_val_t cmd);
|
|
||||||
el_val_t __exec_bg(el_val_t cmd);
|
|
||||||
|
|
||||||
/* Process */
|
|
||||||
el_val_t __exit_program(el_val_t code);
|
|
||||||
|
|
||||||
/* Filesystem */
|
|
||||||
el_val_t __fs_exists(el_val_t path);
|
|
||||||
el_val_t __fs_mkdir(el_val_t path);
|
|
||||||
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_write_bytes(el_val_t path, el_val_t bytes, el_val_t n);
|
|
||||||
el_val_t __fs_list_raw(el_val_t path);
|
|
||||||
|
|
||||||
/* HTTP server */
|
|
||||||
el_val_t __http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
|
||||||
el_val_t __http_serve(el_val_t port, el_val_t handler);
|
|
||||||
el_val_t __http_serve_v2(el_val_t port, el_val_t handler);
|
|
||||||
|
|
||||||
/* HTTP conn fd / SSE (weak; overridden by el_seed.c when linked together) */
|
|
||||||
el_val_t __http_conn_fd(void);
|
|
||||||
el_val_t __http_sse_open(el_val_t conn_id);
|
|
||||||
el_val_t __http_sse_send(el_val_t conn_id, el_val_t data);
|
|
||||||
el_val_t __http_sse_close(el_val_t conn_id);
|
|
||||||
|
|
||||||
/* HTTP client (requires HAVE_CURL; stubs provided for no-curl builds) */
|
|
||||||
el_val_t __http_do(el_val_t method, el_val_t url, el_val_t body,
|
|
||||||
el_val_t headers_map, el_val_t timeout_ms);
|
|
||||||
el_val_t __http_do_map(el_val_t method, el_val_t url, el_val_t body,
|
|
||||||
el_val_t headers_json, el_val_t timeout_ms);
|
|
||||||
el_val_t __http_do_map_to_file(el_val_t method, el_val_t url, el_val_t body,
|
|
||||||
el_val_t headers_json, el_val_t output_path);
|
|
||||||
|
|
||||||
/* JSON */
|
|
||||||
el_val_t __json_array_get(el_val_t json, el_val_t index);
|
|
||||||
el_val_t __json_array_get_string(el_val_t json, el_val_t index);
|
|
||||||
el_val_t __json_array_len(el_val_t json);
|
|
||||||
el_val_t __json_get(el_val_t json, el_val_t key);
|
|
||||||
el_val_t __json_get_raw(el_val_t json, el_val_t key);
|
|
||||||
el_val_t __json_set(el_val_t json, el_val_t key, el_val_t value);
|
|
||||||
el_val_t __json_parse_map(el_val_t json_str);
|
|
||||||
el_val_t __json_stringify_val(el_val_t val);
|
|
||||||
|
|
||||||
/* Hashing */
|
|
||||||
el_val_t __sha256_hex(el_val_t s);
|
|
||||||
|
|
||||||
/* State K/V */
|
|
||||||
el_val_t __state_del(el_val_t key);
|
|
||||||
el_val_t __state_get(el_val_t key);
|
|
||||||
el_val_t __state_keys(void);
|
|
||||||
el_val_t __state_set(el_val_t key, el_val_t val);
|
|
||||||
|
|
||||||
/* UUID */
|
|
||||||
el_val_t __uuid_v4(void);
|
|
||||||
|
|
||||||
/* Args */
|
|
||||||
el_val_t __args_json(void);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,761 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/* SSE connection fd — set by http_worker_v2 before calling the El handler,
|
|
||||||
* cleared afterwards. Defined in el_seed.c; called from el_runtime.c.
|
|
||||||
* The getter is exposed as __http_conn_fd() to El programs. */
|
|
||||||
void el_seed_set_http_conn_fd(int fd);
|
|
||||||
|
|
||||||
/* 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);
|
|
||||||
|
|
||||||
/* ── Calendar + CalendarTime + Rhythm + LocalDate/Time/DateTime ─────────────
|
|
||||||
* Phase 1.5 of the time system. Calendar is pluggable: EarthCalendar (IANA
|
|
||||||
* zones, Gregorian, DST) is the user-facing default; MarsCalendar,
|
|
||||||
* CycleCalendar(period), NoCycleCalendar, RelativeCalendar handle non-Earth
|
|
||||||
* domains.
|
|
||||||
*
|
|
||||||
* A Calendar interprets an Instant under a particular cycle convention and
|
|
||||||
* produces a CalendarTime. CalendarTime carries the underlying Instant and
|
|
||||||
* a back-pointer to its Calendar; arithmetic and formatting consult the
|
|
||||||
* Calendar to convert ns since epoch into year/month/day/hour/minute/second
|
|
||||||
* (or sol/phase, or cycle/phase, depending on kind).
|
|
||||||
*
|
|
||||||
* Storage convention: Calendar / CalendarTime / Rhythm / LocalDate /
|
|
||||||
* LocalDateTime are heap-allocated structs whose pointers are cast into
|
|
||||||
* el_val_t. A 24-bit magic header at offset 0 lets the runtime identify
|
|
||||||
* the kind safely. LocalTime is small enough to live in the int64 slot
|
|
||||||
* directly (nanos since midnight, signed). */
|
|
||||||
|
|
||||||
/* Zone — opaque IANA zone or fixed offset, used by EarthCalendar.
|
|
||||||
* `zone_id` is either an IANA name ("America/New_York", "UTC") or a fixed
|
|
||||||
* offset string ("+05:30", "-08:00"). The runtime resolves it via tzset()
|
|
||||||
* on first use of the owning EarthCalendar. */
|
|
||||||
el_val_t zone(el_val_t id);
|
|
||||||
el_val_t zone_utc(void);
|
|
||||||
el_val_t zone_local(void);
|
|
||||||
el_val_t zone_offset(el_val_t hours, el_val_t minutes);
|
|
||||||
|
|
||||||
/* Calendar constructors. Each returns an el_val_t pointer to a heap-
|
|
||||||
* allocated, magic-tagged Calendar struct. Calendars are interned by
|
|
||||||
* (kind, zone_id, period_ns, epoch_ns) so identical constructors return
|
|
||||||
* the same pointer — equality is reference equality. */
|
|
||||||
el_val_t earth_calendar(el_val_t z);
|
|
||||||
el_val_t earth_calendar_default(void);
|
|
||||||
el_val_t mars_calendar(void);
|
|
||||||
el_val_t cycle_calendar(el_val_t period_dur);
|
|
||||||
el_val_t no_cycle_calendar(void);
|
|
||||||
el_val_t relative_calendar(el_val_t epoch_inst);
|
|
||||||
|
|
||||||
/* CalendarTime constructors and methods. Returns a heap-allocated struct
|
|
||||||
* whose pointer fits in el_val_t. */
|
|
||||||
el_val_t now_in(el_val_t cal);
|
|
||||||
el_val_t in_calendar(el_val_t inst, el_val_t cal);
|
|
||||||
el_val_t cal_format(el_val_t ct, el_val_t pattern);
|
|
||||||
el_val_t cal_to_instant(el_val_t ct);
|
|
||||||
el_val_t cal_cycle_phase(el_val_t ct);
|
|
||||||
el_val_t cal_in(el_val_t ct, el_val_t cal);
|
|
||||||
|
|
||||||
/* LocalDate / LocalTime / LocalDateTime — calendar-agnostic value types.
|
|
||||||
* LocalTime carries nanoseconds since midnight as a signed int64 directly
|
|
||||||
* in the el_val_t slot (no allocation). LocalDate / LocalDateTime are
|
|
||||||
* heap-allocated structs with magic headers. */
|
|
||||||
el_val_t local_date(el_val_t y, el_val_t m, el_val_t d);
|
|
||||||
el_val_t local_time(el_val_t h, el_val_t m, el_val_t s, el_val_t ns);
|
|
||||||
el_val_t local_datetime(el_val_t date, el_val_t time);
|
|
||||||
el_val_t zoned(el_val_t date, el_val_t time, el_val_t cal);
|
|
||||||
|
|
||||||
el_val_t local_date_year(el_val_t ld);
|
|
||||||
el_val_t local_date_month(el_val_t ld);
|
|
||||||
el_val_t local_date_day(el_val_t ld);
|
|
||||||
el_val_t local_time_hour(el_val_t lt);
|
|
||||||
el_val_t local_time_minute(el_val_t lt);
|
|
||||||
el_val_t local_time_second(el_val_t lt);
|
|
||||||
el_val_t local_time_nanos(el_val_t lt);
|
|
||||||
|
|
||||||
el_val_t el_local_date_add_dur(el_val_t ld, el_val_t dur);
|
|
||||||
el_val_t el_local_time_add_dur(el_val_t lt, el_val_t dur);
|
|
||||||
el_val_t el_local_date_lt(el_val_t a, el_val_t b);
|
|
||||||
el_val_t el_local_date_eq(el_val_t a, el_val_t b);
|
|
||||||
|
|
||||||
/* Rhythm — pluggable recurrence AST. Returns a heap-allocated struct
|
|
||||||
* pointer in el_val_t; rhythms are immutable so callers may share them. */
|
|
||||||
el_val_t rhythm_cycle_start(void);
|
|
||||||
el_val_t rhythm_cycle_phase(el_val_t phase);
|
|
||||||
el_val_t rhythm_duration(el_val_t d);
|
|
||||||
el_val_t rhythm_session_start(void);
|
|
||||||
el_val_t rhythm_event(el_val_t name);
|
|
||||||
el_val_t rhythm_and(el_val_t a, el_val_t b);
|
|
||||||
el_val_t rhythm_or(el_val_t a, el_val_t b);
|
|
||||||
el_val_t rhythm_weekday(el_val_t day);
|
|
||||||
el_val_t rhythm_weekly_at(el_val_t day, el_val_t hour, el_val_t minute);
|
|
||||||
el_val_t rhythm_next_after(el_val_t r, el_val_t after, el_val_t cal);
|
|
||||||
el_val_t rhythm_matches(el_val_t r, el_val_t ct);
|
|
||||||
|
|
||||||
/* ── 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);
|
|
||||||
|
|
||||||
/* ── Text-processing primitives (Phase 1: byte/codepoint, ASCII char classes)
|
|
||||||
* Phase 2 (filed): Unicode-grapheme awareness, NFC/NFD normalization, regex.
|
|
||||||
* is_* predicates: empty input returns false; multi-char requires ALL bytes
|
|
||||||
* to match. ASCII ranges only in Phase 1. */
|
|
||||||
|
|
||||||
/* Counting */
|
|
||||||
el_val_t str_count(el_val_t s, el_val_t sub); /* non-overlapping */
|
|
||||||
el_val_t str_count_chars(el_val_t s); /* codepoint count */
|
|
||||||
el_val_t str_count_bytes(el_val_t s); /* alias of str_len */
|
|
||||||
el_val_t str_count_lines(el_val_t s);
|
|
||||||
el_val_t str_count_words(el_val_t s);
|
|
||||||
el_val_t str_count_letters(el_val_t s); /* ASCII [A-Za-z] */
|
|
||||||
el_val_t str_count_digits(el_val_t s); /* ASCII [0-9] */
|
|
||||||
|
|
||||||
/* Find / position */
|
|
||||||
el_val_t str_index_of_all(el_val_t s, el_val_t sub); /* [Int] of byte offsets */
|
|
||||||
el_val_t str_last_index_of(el_val_t s, el_val_t sub);
|
|
||||||
el_val_t str_find_chars(el_val_t s, el_val_t any_of); /* first idx of any ch */
|
|
||||||
|
|
||||||
/* Transform */
|
|
||||||
el_val_t str_repeat(el_val_t s, el_val_t n);
|
|
||||||
el_val_t str_reverse(el_val_t s); /* by codepoint */
|
|
||||||
el_val_t str_strip_prefix(el_val_t s, el_val_t prefix);
|
|
||||||
el_val_t str_strip_suffix(el_val_t s, el_val_t suffix);
|
|
||||||
el_val_t str_strip_chars(el_val_t s, el_val_t chars);
|
|
||||||
el_val_t str_lstrip(el_val_t s);
|
|
||||||
el_val_t str_rstrip(el_val_t s);
|
|
||||||
|
|
||||||
/* Char classification (Bool) */
|
|
||||||
el_val_t is_letter(el_val_t s);
|
|
||||||
el_val_t is_digit(el_val_t s);
|
|
||||||
el_val_t is_alphanumeric(el_val_t s);
|
|
||||||
el_val_t is_whitespace(el_val_t s);
|
|
||||||
el_val_t is_punctuation(el_val_t s);
|
|
||||||
el_val_t is_uppercase(el_val_t s);
|
|
||||||
el_val_t is_lowercase(el_val_t s);
|
|
||||||
|
|
||||||
/* Split / join */
|
|
||||||
el_val_t str_split_lines(el_val_t s);
|
|
||||||
el_val_t str_split_chars(el_val_t s); /* alias of native_string_chars */
|
|
||||||
el_val_t str_split_n(el_val_t s, el_val_t sep, el_val_t n);
|
|
||||||
el_val_t str_join(el_val_t list, el_val_t sep); /* alias of list_join */
|
|
||||||
|
|
||||||
/* ── 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. */
|
|
||||||
/* ── Subprocess execution ────────────────────────────────────────────────── */
|
|
||||||
el_val_t exec_command(el_val_t cmd); /* run shell command, return exit code */
|
|
||||||
el_val_t exec_capture(el_val_t cmd); /* run shell command, capture stdout */
|
|
||||||
el_val_t exec(el_val_t cmd); /* exec(cmd) → stdout String (30s timeout) */
|
|
||||||
el_val_t exec_bg(el_val_t cmd); /* exec_bg(cmd) → PID String (non-blocking) */
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -1202,7 +1202,7 @@ fn codegen_js_inner(stmts: [Map<String, Any>], source: String, bundle_mode: Bool
|
|||||||
js_emit_line(js_strip_es_exports(runtime_content))
|
js_emit_line(js_strip_es_exports(runtime_content))
|
||||||
js_emit_line("")
|
js_emit_line("")
|
||||||
} else {
|
} else {
|
||||||
js_emit_line("// Runtime: foundation/el/el-compiler/runtime/el_runtime.js")
|
js_emit_line("// Runtime: foundation/el/runtime/el_runtime.js")
|
||||||
js_emit_line("import \"./el_runtime.js\";")
|
js_emit_line("import \"./el_runtime.js\";")
|
||||||
}
|
}
|
||||||
// In module mode: destructure all builtins off globalThis.__el so call
|
// In module mode: destructure all builtins off globalThis.__el so call
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -368,13 +368,13 @@ fn main() -> Void {
|
|||||||
let which_out: String = str_trim(exec_capture("which " + elc_bin + " 2>/dev/null"))
|
let which_out: String = str_trim(exec_capture("which " + elc_bin + " 2>/dev/null"))
|
||||||
if !str_eq(which_out, "") {
|
if !str_eq(which_out, "") {
|
||||||
let elc_dir: String = dirname_of(which_out)
|
let elc_dir: String = dirname_of(which_out)
|
||||||
runtime_path = elc_dir + "/../el-compiler/runtime/el_runtime.c"
|
runtime_path = elc_dir + "/../runtime/el_runtime.c"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If --runtime points to a directory, auto-locate el_runtime.c inside it.
|
// If --runtime points to a directory, auto-locate el_runtime.c inside it.
|
||||||
// This lets both forms work:
|
// This lets both forms work:
|
||||||
// --runtime=/opt/el/el-compiler/runtime (directory form)
|
// --runtime=/opt/el/runtime (directory form)
|
||||||
// --runtime=/opt/el/el-compiler/runtime/el_runtime.c (file form)
|
// --runtime=/opt/el/runtime/el_runtime.c (file form)
|
||||||
if !str_eq(runtime_path, "") {
|
if !str_eq(runtime_path, "") {
|
||||||
let is_dir: String = str_trim(exec_capture("test -d " + runtime_path + " && echo dir || echo file"))
|
let is_dir: String = str_trim(exec_capture("test -d " + runtime_path + " && echo dir || echo file"))
|
||||||
if str_eq(is_dir, "dir") {
|
if str_eq(is_dir, "dir") {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
//
|
//
|
||||||
// Compile and run:
|
// Compile and run:
|
||||||
// ./dist/platform/elc examples/html-page.el > /tmp/html-page.c
|
// ./dist/platform/elc examples/html-page.el > /tmp/html-page.c
|
||||||
// cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
// cc -std=c11 -I runtime -lcurl -lpthread \
|
||||||
// -o /tmp/html-page /tmp/html-page.c el-compiler/runtime/el_runtime.c
|
// -o /tmp/html-page /tmp/html-page.c runtime/el_runtime.c
|
||||||
// /tmp/html-page
|
// /tmp/html-page
|
||||||
|
|
||||||
fn render_item(item: String) -> String {
|
fn render_item(item: String) -> String {
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
# El Compiler Release v1.0.0 — 2026-05-02
|
|
||||||
|
|
||||||
## Components
|
|
||||||
- `bootstrap.py` — El language compiler (Python, recursive descent parser, emits C)
|
|
||||||
- `el_runtime.c` — El runtime (C, HTTP server, engram, DHARMA, LLM chain)
|
|
||||||
- `el_runtime.h` — Runtime public API header
|
|
||||||
|
|
||||||
## Changes in this release
|
|
||||||
|
|
||||||
### Critical bug fixes
|
|
||||||
- `state_set`/`state_get` are now thread-safe (pthread_mutex). Was racing across 64 worker threads.
|
|
||||||
- `looks_like_string` threshold raised from 1,000,000 to 4GB. Unix timestamps were being dereferenced as heap pointers.
|
|
||||||
- `fs_read` guards against negative `ftell` result (pipe/special file overflow).
|
|
||||||
|
|
||||||
### Engram architecture (major)
|
|
||||||
- Two-layer activation: `background_activation` (Layer 1, broad fan-out) + `working_memory_weight` (Layer 2, executive filter)
|
|
||||||
- Inhibitory edges: `EngramEdge.inhibitory` flag suppresses working memory promotion without affecting background activation
|
|
||||||
- Suppression memory: `suppression_count` — nodes activated-but-suppressed accumulate pressure toward breakthrough
|
|
||||||
- Temporal decay: `temporal_decay_rate`, `created_at`, `last_activated_at`, `activation_count` on EngramNode
|
|
||||||
- Per-type activation thresholds (Safety: 0.05, Canonical: 0.15, Lesson: 0.25, Note: 0.40)
|
|
||||||
- Temporal range query: `engram_query_range(start_ms, end_ms)`
|
|
||||||
- Layered consciousness: `EngramLayer` struct, `layer_id` on nodes and edges, `EngramStore.layers[]`
|
|
||||||
- Layer 0 override pass: safety layer fires last and cannot be suppressed
|
|
||||||
|
|
||||||
## SHA256
|
|
||||||
bootstrap.py
|
|
||||||
el_runtime.c
|
|
||||||
el_runtime.h
|
|
||||||
@@ -132,7 +132,7 @@ if [[ $LVGL_OK -eq 1 ]]; then
|
|||||||
else
|
else
|
||||||
_miss "LVGL/MCU" "-DEL_TARGET_LVGL (lvgl.h not found)"
|
_miss "LVGL/MCU" "-DEL_TARGET_LVGL (lvgl.h not found)"
|
||||||
echo " Install: git clone https://github.com/lvgl/lvgl"
|
echo " Install: git clone https://github.com/lvgl/lvgl"
|
||||||
echo " (place lvgl/ next to el-compiler/runtime/)"
|
echo " (place lvgl/ next to runtime/)"
|
||||||
MISSING=$((MISSING + 1))
|
MISSING=$((MISSING + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -34,12 +34,12 @@
|
|||||||
* pq_hybrid_* and HKDF-SHA256 derivation.
|
* pq_hybrid_* and HKDF-SHA256 derivation.
|
||||||
*
|
*
|
||||||
* Canonical compile command:
|
* Canonical compile command:
|
||||||
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
* cc -std=c11 -I runtime -lcurl -lpthread \
|
||||||
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
* -o <out> <prog>.c runtime/el_runtime.c
|
||||||
*
|
*
|
||||||
* With liboqs (post-quantum stack):
|
* With liboqs (post-quantum stack):
|
||||||
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread -loqs -lcrypto \
|
* cc -std=c11 -I runtime -lcurl -lpthread -loqs -lcrypto \
|
||||||
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
* -o <out> <prog>.c runtime/el_runtime.c
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -117,6 +117,15 @@ el_val_t el_min(el_val_t a, el_val_t b);
|
|||||||
void el_retain(el_val_t v);
|
void el_retain(el_val_t v);
|
||||||
void el_release(el_val_t v);
|
void el_release(el_val_t v);
|
||||||
|
|
||||||
|
/* ── Arena scoping ────────────────────────────────────────────────────────────
|
||||||
|
* el_arena_push() activates the string arena (if not already active) and
|
||||||
|
* returns a mark; el_arena_pop(mark) frees all strings allocated since that
|
||||||
|
* mark. Used by codegen for per-function/statement scoping and by long-running
|
||||||
|
* EL loops (e.g. the soul daemon's awareness tick) to reclaim per-iteration
|
||||||
|
* allocations. */
|
||||||
|
el_val_t el_arena_push(void);
|
||||||
|
el_val_t el_arena_pop(el_val_t mark);
|
||||||
|
|
||||||
/* ── List ────────────────────────────────────────────────────────────────── */
|
/* ── List ────────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
el_val_t el_list_new(el_val_t count, ...);
|
el_val_t el_list_new(el_val_t count, ...);
|
||||||
@@ -142,6 +151,7 @@ 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_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_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);
|
el_val_t http_delete(el_val_t url);
|
||||||
|
el_val_t http_delete_json(el_val_t url, el_val_t json_body);
|
||||||
void http_serve(el_val_t port, el_val_t handler);
|
void http_serve(el_val_t port, el_val_t handler);
|
||||||
void http_set_handler(el_val_t name);
|
void http_set_handler(el_val_t name);
|
||||||
|
|
||||||
@@ -167,6 +177,11 @@ void http_set_handler(el_val_t name);
|
|||||||
void http_serve_v2(el_val_t port, el_val_t handler);
|
void http_serve_v2(el_val_t port, el_val_t handler);
|
||||||
void http_set_handler_v2(el_val_t name);
|
void http_set_handler_v2(el_val_t name);
|
||||||
|
|
||||||
|
/* Non-blocking variant of http_serve: runs the accept loop in a background
|
||||||
|
* pthread and returns immediately so the caller can continue (used by the
|
||||||
|
* soul daemon to run awareness_run() after starting its HTTP API). */
|
||||||
|
void http_serve_async(el_val_t port, el_val_t handler);
|
||||||
|
|
||||||
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
||||||
* literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The
|
* literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The
|
||||||
* returned string carries the discriminator `{"el_http_response":1,...}`
|
* returned string carries the discriminator `{"el_http_response":1,...}`
|
||||||
@@ -576,6 +591,7 @@ el_val_t engram_list_layers(void);
|
|||||||
el_val_t engram_get_node(el_val_t id);
|
el_val_t engram_get_node(el_val_t id);
|
||||||
void engram_strengthen(el_val_t node_id);
|
void engram_strengthen(el_val_t node_id);
|
||||||
void engram_forget(el_val_t node_id);
|
void engram_forget(el_val_t node_id);
|
||||||
|
el_val_t engram_prune_telemetry(el_val_t older_than_ms);
|
||||||
el_val_t engram_node_count(void);
|
el_val_t engram_node_count(void);
|
||||||
el_val_t engram_search(el_val_t query, el_val_t limit);
|
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);
|
el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset);
|
||||||
@@ -594,20 +610,48 @@ 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);
|
||||||
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
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_activate_json(el_val_t query, el_val_t depth);
|
||||||
el_val_t engram_stats_json(void);
|
el_val_t engram_stats_json(void);
|
||||||
|
el_val_t engram_act_stats_json(void);
|
||||||
|
el_val_t engram_text_health_json(void);
|
||||||
|
el_val_t engram_cosine_sim(el_val_t id_a, el_val_t id_b);
|
||||||
|
/* Destructively pop up to `max` newly-formed Hebbian associations as a JSON
|
||||||
|
* array of {from_id,to_id,weight,hebb}. The learning process (soul daemon) is
|
||||||
|
* not the process that owns persistence (engram HTTP server); this is how a
|
||||||
|
* self-formed association crosses that boundary. (2026-08-07 self-review.) */
|
||||||
|
el_val_t engram_hebb_drain_json(el_val_t max);
|
||||||
|
/* Document frequency of a term across node labels — term-specificity signal
|
||||||
|
* for curiosity seed selection. (2026-08-03 self-review.) */
|
||||||
|
el_val_t engram_label_df(el_val_t term);
|
||||||
|
el_val_t engram_embed_backfill(el_val_t count);
|
||||||
el_val_t engram_list_layers_json(void);
|
el_val_t engram_list_layers_json(void);
|
||||||
/* Working memory introspection — count, mean weight, and top-N snapshot.
|
/* Working memory introspection — count, mean weight, and top-N snapshot.
|
||||||
* Ported from el-compiler/runtime on 2026-06-30 self-review. */
|
* Ported from runtime on 2026-06-30 self-review. */
|
||||||
el_val_t engram_wm_count(void);
|
el_val_t engram_wm_count(void);
|
||||||
el_val_t engram_wm_avg_weight(void);
|
el_val_t engram_wm_avg_weight(void);
|
||||||
el_val_t engram_wm_top_json(el_val_t n);
|
el_val_t engram_wm_top_json(el_val_t n);
|
||||||
/* Merge-load: add nodes/edges from a snapshot without resetting the store. */
|
/* Merge-load: add nodes/edges from a snapshot without resetting the store. */
|
||||||
el_val_t engram_load_merge(el_val_t path);
|
el_val_t engram_load_merge(el_val_t path);
|
||||||
|
|
||||||
|
/* ── WAL + compaction + integrity (ENGRAM_WAL=on; design doc §§3-14,§18) ──── */
|
||||||
|
int engram_wal_enabled(void);
|
||||||
|
el_val_t engram_crc32(el_val_t s);
|
||||||
|
el_val_t engram_wal_boot(el_val_t dir); /* replay + open; returns records */
|
||||||
|
el_val_t engram_wal_open_dir(el_val_t dir);
|
||||||
|
el_val_t engram_wal_node_put(el_val_t dir, el_val_t id);
|
||||||
|
el_val_t engram_wal_edges_since(el_val_t dir, el_val_t start_count);
|
||||||
|
el_val_t engram_wal_hebb_batch(el_val_t dir, el_val_t start_count);
|
||||||
|
el_val_t engram_wal_forget(el_val_t dir, el_val_t id);
|
||||||
|
el_val_t engram_wal_compact(el_val_t dir);
|
||||||
|
el_val_t engram_wal_maybe_compact(el_val_t dir);
|
||||||
|
el_val_t engram_resolve_data_dir(void); /* §18.2 fail-loud default */
|
||||||
|
el_val_t engram_is_protected(el_val_t id); /* §18.1/18.3 derived set */
|
||||||
|
el_val_t engram_protected_json(void);
|
||||||
/* engram_compile_layered_json — produce a prompt-ready text block split
|
/* engram_compile_layered_json — produce a prompt-ready text block split
|
||||||
* into "[LAYER 0 — STRUCTURAL]" (non-suppressible layers, sacred fire)
|
* into "[LAYER 0 — STRUCTURAL]" (non-suppressible layers, sacred fire)
|
||||||
* and "[ENGRAM CONTEXT]" (standard suppressible layers). Returns "" if
|
* and "[ENGRAM CONTEXT]" (standard suppressible layers). Returns "" if
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
* Threading: __thread_create / __thread_join use dlsym(RTLD_DEFAULT) to look
|
* Threading: __thread_create / __thread_join use dlsym(RTLD_DEFAULT) to look
|
||||||
* up El function symbols at runtime. This is the foundation of El's parallelism.
|
* up El function symbols at runtime. This is the foundation of El's parallelism.
|
||||||
*
|
*
|
||||||
* Link: cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
* Link: cc -std=c11 -I runtime -lcurl -lpthread \
|
||||||
* -o <out> <prog>.c el_seed.c
|
* -o <out> <prog>.c el_seed.c
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
//
|
//
|
||||||
// Thin El wrappers over seed JSON primitives, plus pure-El builders and
|
// Thin El wrappers over seed JSON primitives, plus pure-El builders and
|
||||||
// helpers. Each function here corresponds to (and replaces) a C function
|
// helpers. Each function here corresponds to (and replaces) a C function
|
||||||
// from el-compiler/runtime/legacy/el_runtime.c (lines 2692–3333).
|
// from runtime/el_runtime.c (lines 2692–3333).
|
||||||
//
|
//
|
||||||
// Seed primitives consumed by this module:
|
// Seed primitives consumed by this module:
|
||||||
// __json_get(json, key) -> String (value as string)
|
// __json_get(json, key) -> String (value as string)
|
||||||
|
|||||||
@@ -25,8 +25,8 @@
|
|||||||
// runtime/collections.el \
|
// runtime/collections.el \
|
||||||
// <user-program.el> > combined.el
|
// <user-program.el> > combined.el
|
||||||
// ./dist/platform/elc combined.el > output.c
|
// ./dist/platform/elc combined.el > output.c
|
||||||
// cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
// cc -std=c11 -I runtime -lcurl -lpthread \
|
||||||
// -o output output.c el-compiler/runtime/el_seed.c
|
// -o output output.c runtime/el_seed.c
|
||||||
|
|
||||||
// This file itself is not compiled — it is documentation only.
|
// This file itself is not compiled — it is documentation only.
|
||||||
fn runtime_version() -> String {
|
fn runtime_version() -> String {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// runtime/math.el — Float math, integer utilities, and numeric conversions.
|
// runtime/math.el — Float math, integer utilities, and numeric conversions.
|
||||||
//
|
//
|
||||||
// Implements the math/float surface from el-compiler/runtime/legacy/el_runtime.c
|
// Implements the math/float surface from runtime/el_runtime.c
|
||||||
// (lines 303–305 for el_abs/max/min, lines 4725–4771 for float/format ops)
|
// (lines 303–305 for el_abs/max/min, lines 4725–4771 for float/format ops)
|
||||||
// in pure El, using seed primitives.
|
// in pure El, using seed primitives.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// runtime/time.el — Time operations, sleep, and formatting.
|
// runtime/time.el — Time operations, sleep, and formatting.
|
||||||
//
|
//
|
||||||
// Implements the time surface from el-compiler/runtime/legacy/el_runtime.c
|
// Implements the time surface from runtime/el_runtime.c
|
||||||
// (lines 3334–3440, 3471–3656) in pure El, using seed primitives.
|
// (lines 3334–3440, 3471–3656) in pure El, using seed primitives.
|
||||||
//
|
//
|
||||||
// Seed primitives consumed:
|
// Seed primitives consumed:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ cd "$(dirname "$0")"
|
|||||||
|
|
||||||
EL_HOME="${EL_HOME:-$(cd ../.. && pwd)}"
|
EL_HOME="${EL_HOME:-$(cd ../.. && pwd)}"
|
||||||
ELC="${EL_HOME}/dist/platform/elc"
|
ELC="${EL_HOME}/dist/platform/elc"
|
||||||
RUNTIME_DIR="${EL_HOME}/el-compiler/runtime"
|
RUNTIME_DIR="${EL_HOME}/runtime"
|
||||||
|
|
||||||
if [ ! -x "${ELC}" ]; then
|
if [ ! -x "${ELC}" ]; then
|
||||||
echo "elc not found at ${ELC}" >&2
|
echo "elc not found at ${ELC}" >&2
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ cd "$(dirname "$0")"
|
|||||||
|
|
||||||
EL_HOME="${EL_HOME:-$(cd ../.. && pwd)}"
|
EL_HOME="${EL_HOME:-$(cd ../.. && pwd)}"
|
||||||
ELC="${EL_HOME}/dist/platform/elc"
|
ELC="${EL_HOME}/dist/platform/elc"
|
||||||
RUNTIME_DIR="${EL_HOME}/el-compiler/runtime"
|
RUNTIME_DIR="${EL_HOME}/runtime"
|
||||||
|
|
||||||
if [ ! -x "${ELC}" ]; then
|
if [ ! -x "${ELC}" ]; then
|
||||||
echo "elc not found at ${ELC}" >&2
|
echo "elc not found at ${ELC}" >&2
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
// tests/runtime/string_test.el > /tmp/string_test_combined.el
|
// tests/runtime/string_test.el > /tmp/string_test_combined.el
|
||||||
//
|
//
|
||||||
// ./dist/platform/elc /tmp/string_test_combined.el > /tmp/string_test.c
|
// ./dist/platform/elc /tmp/string_test_combined.el > /tmp/string_test.c
|
||||||
// cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
// cc -std=c11 -I runtime -lcurl -lpthread \
|
||||||
// -o /tmp/string_test /tmp/string_test.c el-compiler/runtime/el_seed.c
|
// -o /tmp/string_test /tmp/string_test.c runtime/el_seed.c
|
||||||
// /tmp/string_test; echo "exit: $?"
|
// /tmp/string_test; echo "exit: $?"
|
||||||
//
|
//
|
||||||
// Exit code equals the number of failing assertions (0 = all pass).
|
// Exit code equals the number of failing assertions (0 = all pass).
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ cd "$(dirname "$0")"
|
|||||||
|
|
||||||
EL_HOME="${EL_HOME:-$(cd ../.. && pwd)}"
|
EL_HOME="${EL_HOME:-$(cd ../.. && pwd)}"
|
||||||
ELC="${ELC:-${EL_HOME}/dist/platform/elc}"
|
ELC="${ELC:-${EL_HOME}/dist/platform/elc}"
|
||||||
RUNTIME_DIR="${EL_HOME}/el-compiler/runtime"
|
RUNTIME_DIR="${EL_HOME}/runtime"
|
||||||
|
|
||||||
if [ ! -x "${ELC}" ]; then
|
if [ ! -x "${ELC}" ]; then
|
||||||
echo "elc not found at ${ELC}" >&2
|
echo "elc not found at ${ELC}" >&2
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ cd "$(dirname "$0")"
|
|||||||
|
|
||||||
EL_HOME="${EL_HOME:-$(cd ../.. && pwd)}"
|
EL_HOME="${EL_HOME:-$(cd ../.. && pwd)}"
|
||||||
ELC="${EL_HOME}/dist/platform/elc"
|
ELC="${EL_HOME}/dist/platform/elc"
|
||||||
RUNTIME_DIR="${EL_HOME}/el-compiler/runtime"
|
RUNTIME_DIR="${EL_HOME}/runtime"
|
||||||
|
|
||||||
if [ ! -x "${ELC}" ]; then
|
if [ ! -x "${ELC}" ]; then
|
||||||
echo "elc not found at ${ELC}" >&2
|
echo "elc not found at ${ELC}" >&2
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ INCLUDE_DIR="${PREFIX}/include"
|
|||||||
LIB_DIR="${PREFIX}/lib"
|
LIB_DIR="${PREFIX}/lib"
|
||||||
|
|
||||||
ELC_SRC="${EL_ROOT}/dist/platform/elc"
|
ELC_SRC="${EL_ROOT}/dist/platform/elc"
|
||||||
RUNTIME_SRC="${EL_ROOT}/el-compiler/runtime"
|
RUNTIME_SRC="${EL_ROOT}/runtime"
|
||||||
STDLIB_SRC="${EL_ROOT}/runtime"
|
STDLIB_SRC="${EL_ROOT}/runtime"
|
||||||
|
|
||||||
echo "==> Installing El framework to ${PREFIX}"
|
echo "==> Installing El framework to ${PREFIX}"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ fi
|
|||||||
# Discover el_runtime.c
|
# Discover el_runtime.c
|
||||||
EL_RUNTIME=""
|
EL_RUNTIME=""
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
LOCAL_RUNTIME="${SCRIPT_DIR}/../el-compiler/runtime/el_runtime.c"
|
LOCAL_RUNTIME="${SCRIPT_DIR}/../runtime/el_runtime.c"
|
||||||
if [[ -f "${LOCAL_RUNTIME}" ]]; then
|
if [[ -f "${LOCAL_RUNTIME}" ]]; then
|
||||||
EL_RUNTIME="$(cd "$(dirname "${LOCAL_RUNTIME}")" && pwd)/$(basename "${LOCAL_RUNTIME}")"
|
EL_RUNTIME="$(cd "$(dirname "${LOCAL_RUNTIME}")" && pwd)/$(basename "${LOCAL_RUNTIME}")"
|
||||||
EL_INCLUDE="$(dirname "${EL_RUNTIME}")"
|
EL_INCLUDE="$(dirname "${EL_RUNTIME}")"
|
||||||
|
|||||||
Executable
+94
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# check-single-runtime.sh — CODE-VS-ARTIFACT drift guard for the el runtime.
|
||||||
|
#
|
||||||
|
# Enforces org policy docs/CODE-VS-ARTIFACT.md rule #1 (single source of truth):
|
||||||
|
# there is exactly ONE authored el_runtime.c, and it lives at lang/runtime/.
|
||||||
|
# Any other el_runtime.c in the tree is a fork (a hand-synced copy). A lagging
|
||||||
|
# fork is exactly what shipped to prod and dropped learned `hebb` edges on
|
||||||
|
# restart — this guard exists to make that class of bug impossible to reintroduce.
|
||||||
|
#
|
||||||
|
# Exemptions:
|
||||||
|
# * Build output — generated amalgamations under any dist/ or build/ dir are
|
||||||
|
# artifacts, not sources.
|
||||||
|
# * A small, explicit ALLOWLIST of pre-existing example-app vendored/staging
|
||||||
|
# copies (see below). These are KNOWN DEFERRED DEBT, tracked separately from
|
||||||
|
# the SDK/CI-published runtime. They do NOT ship to prod. The guard warns on
|
||||||
|
# them (visible, greppable) but does not fail — while HARD-FAILING on any new
|
||||||
|
# or non-allowlisted fork, including any return of lang/el-compiler/runtime/
|
||||||
|
# or a lang/releases/ vendored copy.
|
||||||
|
#
|
||||||
|
# Wire-in: run from the repo root in CI (see note at bottom). Exits non-zero on drift.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
CANONICAL="lang/runtime/el_runtime.c"
|
||||||
|
|
||||||
|
# KNOWN DEFERRED example-app forks — remove these as a follow-up, then delete
|
||||||
|
# this allowlist. iOS + Docker copies are regenerated by their build scripts
|
||||||
|
# (cp from lang/runtime) and can be git-rm'd now; the Android jni/ copy is a
|
||||||
|
# committed source its CMake build depends on and needs a build-script change
|
||||||
|
# (cp from lang/runtime) before removal. Tracked in docs/CODE-VS-ARTIFACT.md.
|
||||||
|
ALLOWLIST=(
|
||||||
|
"ui/examples/native-hello-android/app/src/main/jni/el_runtime.c"
|
||||||
|
"ui/examples/native-hello-ios/NativeHello/el_runtime.c"
|
||||||
|
"ui/examples/native-hello/build-docker/runtime/el_runtime.c"
|
||||||
|
)
|
||||||
|
|
||||||
|
is_allowlisted() {
|
||||||
|
local p="$1"
|
||||||
|
for a in "${ALLOWLIST[@]}"; do [ "$p" = "$a" ] && return 0; done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f "$CANONICAL" ]; then
|
||||||
|
echo "FATAL: canonical runtime source missing: $CANONICAL" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# All el_runtime.c files tracked by git, excluding build output (dist/ , build/)
|
||||||
|
# and the canonical source itself.
|
||||||
|
mapfile -t CANDIDATES < <(
|
||||||
|
git ls-files '*el_runtime.c' \
|
||||||
|
| grep -Ev '(^|/)(dist|build)/' \
|
||||||
|
| grep -vx "$CANONICAL" || true
|
||||||
|
)
|
||||||
|
|
||||||
|
FORKS=()
|
||||||
|
DEFERRED=()
|
||||||
|
for f in "${CANDIDATES[@]}"; do
|
||||||
|
if is_allowlisted "$f"; then DEFERRED+=("$f"); else FORKS+=("$f"); fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${#DEFERRED[@]}" -gt 0 ]; then
|
||||||
|
echo "WARN: allowlisted (deferred) el_runtime.c forks still present — clean these up:" >&2
|
||||||
|
for f in "${DEFERRED[@]}"; do echo " - $f" >&2; done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#FORKS[@]}" -gt 0 ]; then
|
||||||
|
echo "FATAL: el_runtime.c fork(s) detected outside the canonical location." >&2
|
||||||
|
echo " Canonical (the ONLY allowed source): $CANONICAL" >&2
|
||||||
|
echo " Offending copies:" >&2
|
||||||
|
for f in "${FORKS[@]}"; do echo " - $f" >&2; done
|
||||||
|
echo "" >&2
|
||||||
|
echo "Consumers must build against $CANONICAL (pin by git ref where" >&2
|
||||||
|
echo "reproducibility matters) — never a hand-maintained copy." >&2
|
||||||
|
echo "See docs/CODE-VS-ARTIFACT.md." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK: single canonical runtime source — $CANONICAL (no un-allowlisted forks)."
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# CI wire-in:
|
||||||
|
# foundation/el .gitea/workflows/ci-dev.yaml, ci-stage.yaml, sdk-release.yaml
|
||||||
|
# Add an early step (before the build/publish steps). It must run from the
|
||||||
|
# REPO ROOT, so override the job's `defaults.run.working-directory: lang`:
|
||||||
|
#
|
||||||
|
# - name: Guard - single canonical runtime source
|
||||||
|
# working-directory: ${{ github.workspace }}
|
||||||
|
# run: bash scripts/check-single-runtime.sh
|
||||||
|
#
|
||||||
|
# Also add to .githooks/pre-commit so drift is caught before it is committed.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
@@ -18,7 +18,7 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
EL_RUNTIME="${EL_LANG_ROOT}/runtime"
|
||||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||||
EL_APP_ENTRY="${EL_UI_ROOT}/examples/native-hello/src/main.el"
|
EL_APP_ENTRY="${EL_UI_ROOT}/examples/native-hello/src/main.el"
|
||||||
EL_MANIFEST="${EL_UI_ROOT}/examples/native-hello/manifest.el"
|
EL_MANIFEST="${EL_UI_ROOT}/examples/native-hello/manifest.el"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
EL_RUNTIME="${EL_LANG_ROOT}/runtime"
|
||||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||||
EL_APP_ENTRY="${EL_UI_ROOT}/examples/native-hello/src/main.el"
|
EL_APP_ENTRY="${EL_UI_ROOT}/examples/native-hello/src/main.el"
|
||||||
EL_MANIFEST="${EL_UI_ROOT}/examples/native-hello/manifest.el"
|
EL_MANIFEST="${EL_UI_ROOT}/examples/native-hello/manifest.el"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
EL_RUNTIME="${EL_LANG_ROOT}/runtime"
|
||||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build-gtk4"
|
BUILD_DIR="${SCRIPT_DIR}/build-gtk4"
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
EL_RUNTIME="${EL_LANG_ROOT}/runtime"
|
||||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build"
|
BUILD_DIR="${SCRIPT_DIR}/build"
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
EL_RUNTIME="${EL_LANG_ROOT}/runtime"
|
||||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build-win32"
|
BUILD_DIR="${SCRIPT_DIR}/build-win32"
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
EL_RUNTIME="${EL_LANG_ROOT}/runtime"
|
||||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build"
|
BUILD_DIR="${SCRIPT_DIR}/build"
|
||||||
|
|
||||||
@@ -193,6 +193,6 @@ run() {
|
|||||||
case "${1:-run}" in
|
case "${1:-run}" in
|
||||||
clean) clean ;;
|
clean) clean ;;
|
||||||
compile) compile ;;
|
compile) compile ;;
|
||||||
platforms) "${EL_LANG_ROOT}/el-compiler/runtime/detect-platforms" ;;
|
platforms) "${EL_LANG_ROOT}/runtime/detect-platforms" ;;
|
||||||
run|*) run ;;
|
run|*) run ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
|
||||||
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
EL_UI_ROOT="${SCRIPT_DIR}/../.."
|
||||||
EL_RUNTIME="${EL_LANG_ROOT}/el-compiler/runtime"
|
EL_RUNTIME="${EL_LANG_ROOT}/runtime"
|
||||||
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build"
|
BUILD_DIR="${SCRIPT_DIR}/build"
|
||||||
DOCKER_CTX="${SCRIPT_DIR}/build-docker"
|
DOCKER_CTX="${SCRIPT_DIR}/build-docker"
|
||||||
|
|||||||
Reference in New Issue
Block a user