Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b212e9443c | |||
| addd51209f | |||
| 9a13547fe2 | |||
| 481badf1d1 | |||
| b92ec92c48 | |||
| e1bc6fe944 | |||
| eb13dace9c | |||
| 8c2406ff6b | |||
| 99ef855b98 | |||
| 45325f7391 |
@@ -19,6 +19,16 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Guards must run from the REPO ROOT — 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
|
||||||
|
|
||||||
|
- name: Guard - el_runtime.c growth budget
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
run: bash scripts/check-runtime-growth.sh
|
||||||
|
|
||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
run: |
|
run: |
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
@@ -41,7 +51,7 @@ jobs:
|
|||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I runtime \
|
-I runtime \
|
||||||
dist/elc-gen2.c \
|
dist/elc-gen2.c \
|
||||||
runtime/el_runtime.c \
|
$(../scripts/el-runtime-sources.sh runtime) \
|
||||||
-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
|
||||||
@@ -56,7 +66,7 @@ jobs:
|
|||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I runtime \
|
-I runtime \
|
||||||
dist/elb.c \
|
dist/elb.c \
|
||||||
runtime/el_runtime.c \
|
$(../scripts/el-runtime-sources.sh runtime) \
|
||||||
-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
|
||||||
@@ -87,14 +97,20 @@ jobs:
|
|||||||
bash tests/html_sanitizer/run.sh
|
bash tests/html_sanitizer/run.sh
|
||||||
|
|
||||||
# Native El test suites (elc --test, compile-link-run)
|
# Native El test suites (elc --test, compile-link-run)
|
||||||
# el_runtime.c is precompiled to .o once and reused by all 8 modules.
|
# The runtime is MULTI-FILE (see lang/runtime/SOURCES). Every .c is compiled
|
||||||
- name: Precompile el_runtime.o
|
# once into /tmp/libel.a and reused by all 8 test modules — compile-once,
|
||||||
|
# link-many, as prescribed in DESIGN.md. Linking el_runtime.c alone fails
|
||||||
|
# at `ld`: it calls into all six engram sibling TUs.
|
||||||
|
- name: Precompile runtime into libel.a
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
RUNTIME="$(pwd)/runtime"
|
RUNTIME="$(pwd)/runtime"
|
||||||
gcc -O2 -c -I "$RUNTIME" "$RUNTIME/el_runtime.c" \
|
rm -rf /tmp/elrt && mkdir -p /tmp/elrt
|
||||||
-o /tmp/el_runtime.o
|
for src in $(../scripts/el-runtime-sources.sh --check "$RUNTIME"); do
|
||||||
echo "el_runtime.o compiled"
|
gcc -O2 -c -I "$RUNTIME" "$src" -o "/tmp/elrt/$(basename "${src%.c}").o"
|
||||||
|
done
|
||||||
|
ar rcs /tmp/libel.a /tmp/elrt/*.o
|
||||||
|
echo "libel.a built from $(ls /tmp/elrt/*.o | wc -l) translation units"
|
||||||
|
|
||||||
- name: Run tests - native (core)
|
- name: Run tests - native (core)
|
||||||
run: |
|
run: |
|
||||||
@@ -102,7 +118,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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/libel.a \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
||||||
/tmp/el_native_core
|
/tmp/el_native_core
|
||||||
|
|
||||||
@@ -112,7 +128,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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/libel.a \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
||||||
/tmp/el_native_text
|
/tmp/el_native_text
|
||||||
|
|
||||||
@@ -122,7 +138,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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/libel.a \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
||||||
/tmp/el_native_string
|
/tmp/el_native_string
|
||||||
|
|
||||||
@@ -132,7 +148,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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/libel.a \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
||||||
/tmp/el_native_math
|
/tmp/el_native_math
|
||||||
|
|
||||||
@@ -142,7 +158,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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/libel.a \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
||||||
/tmp/el_native_state
|
/tmp/el_native_state
|
||||||
|
|
||||||
@@ -152,7 +168,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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/libel.a \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
||||||
/tmp/el_native_time
|
/tmp/el_native_time
|
||||||
|
|
||||||
@@ -162,7 +178,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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/libel.a \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
||||||
/tmp/el_native_json
|
/tmp/el_native_json
|
||||||
|
|
||||||
@@ -172,7 +188,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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/libel.a \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
||||||
/tmp/el_native_env
|
/tmp/el_native_env
|
||||||
|
|
||||||
@@ -182,7 +198,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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/libel.a \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
||||||
/tmp/el_native_fs
|
/tmp/el_native_fs
|
||||||
|
|
||||||
@@ -306,8 +322,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 runtime/el_runtime.c /opt/el/runtime/el_runtime.c
|
# Whole runtime link set — el_runtime.c alone does not link (it calls
|
||||||
COPY runtime/el_runtime.h /opt/el/runtime/el_runtime.h
|
# into the six engram sibling TUs). See lang/runtime/SOURCES.
|
||||||
|
COPY runtime/ /opt/el/runtime/
|
||||||
COPY runtime/el_runtime.js /opt/el/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
|
||||||
|
|||||||
@@ -29,6 +29,16 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Source branch check passed: ${SOURCE} -> stage"
|
echo "Source branch check passed: ${SOURCE} -> stage"
|
||||||
|
|
||||||
|
# Guards must run from the REPO ROOT — 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
|
||||||
|
|
||||||
|
- name: Guard - el_runtime.c growth budget
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
run: bash scripts/check-runtime-growth.sh
|
||||||
|
|
||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
run: |
|
run: |
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
@@ -48,7 +58,7 @@ jobs:
|
|||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I runtime \
|
-I runtime \
|
||||||
dist/elc-gen2.c \
|
dist/elc-gen2.c \
|
||||||
runtime/el_runtime.c \
|
$(../scripts/el-runtime-sources.sh runtime) \
|
||||||
-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
|
||||||
@@ -86,7 +96,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
||||||
/tmp/el_native_core
|
/tmp/el_native_core
|
||||||
|
|
||||||
@@ -96,7 +106,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
||||||
/tmp/el_native_text
|
/tmp/el_native_text
|
||||||
|
|
||||||
@@ -106,7 +116,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
||||||
/tmp/el_native_string
|
/tmp/el_native_string
|
||||||
|
|
||||||
@@ -116,7 +126,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
||||||
/tmp/el_native_math
|
/tmp/el_native_math
|
||||||
|
|
||||||
@@ -126,7 +136,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
||||||
/tmp/el_native_state
|
/tmp/el_native_state
|
||||||
|
|
||||||
@@ -136,7 +146,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
||||||
/tmp/el_native_time
|
/tmp/el_native_time
|
||||||
|
|
||||||
@@ -146,7 +156,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
||||||
/tmp/el_native_json
|
/tmp/el_native_json
|
||||||
|
|
||||||
@@ -156,7 +166,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
||||||
/tmp/el_native_env
|
/tmp/el_native_env
|
||||||
|
|
||||||
@@ -166,7 +176,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
||||||
/tmp/el_native_fs
|
/tmp/el_native_fs
|
||||||
|
|
||||||
@@ -178,7 +188,7 @@ jobs:
|
|||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I runtime \
|
-I runtime \
|
||||||
dist/elb.c \
|
dist/elb.c \
|
||||||
runtime/el_runtime.c \
|
$(../scripts/el-runtime-sources.sh runtime) \
|
||||||
-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
|
||||||
@@ -290,8 +300,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 runtime/el_runtime.c /opt/el/runtime/el_runtime.c
|
# Whole runtime link set — el_runtime.c alone does not link (it calls
|
||||||
COPY runtime/el_runtime.h /opt/el/runtime/el_runtime.h
|
# into the six engram sibling TUs). See lang/runtime/SOURCES.
|
||||||
|
COPY runtime/ /opt/el/runtime/
|
||||||
COPY runtime/el_runtime.js /opt/el/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
|
||||||
|
|||||||
@@ -29,6 +29,16 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Source branch check passed: ${SOURCE} -> main"
|
echo "Source branch check passed: ${SOURCE} -> main"
|
||||||
|
|
||||||
|
# Guards must run from the REPO ROOT — 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
|
||||||
|
|
||||||
|
- name: Guard - el_runtime.c growth budget
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
run: bash scripts/check-runtime-growth.sh
|
||||||
|
|
||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
run: |
|
run: |
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
@@ -49,7 +59,7 @@ jobs:
|
|||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I runtime \
|
-I runtime \
|
||||||
dist/elc-gen2.c \
|
dist/elc-gen2.c \
|
||||||
runtime/el_runtime.c \
|
$(../scripts/el-runtime-sources.sh runtime) \
|
||||||
-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
|
||||||
@@ -64,7 +74,7 @@ jobs:
|
|||||||
gcc -O2 \
|
gcc -O2 \
|
||||||
-I runtime \
|
-I runtime \
|
||||||
dist/elb.c \
|
dist/elb.c \
|
||||||
runtime/el_runtime.c \
|
$(../scripts/el-runtime-sources.sh runtime) \
|
||||||
-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
|
||||||
@@ -123,7 +133,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_core
|
||||||
/tmp/el_native_core
|
/tmp/el_native_core
|
||||||
|
|
||||||
@@ -133,7 +143,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_text
|
||||||
/tmp/el_native_text
|
/tmp/el_native_text
|
||||||
|
|
||||||
@@ -143,7 +153,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_string
|
||||||
/tmp/el_native_string
|
/tmp/el_native_string
|
||||||
|
|
||||||
@@ -153,7 +163,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_math
|
||||||
/tmp/el_native_math
|
/tmp/el_native_math
|
||||||
|
|
||||||
@@ -163,7 +173,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_state
|
||||||
/tmp/el_native_state
|
/tmp/el_native_state
|
||||||
|
|
||||||
@@ -173,7 +183,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_time
|
||||||
/tmp/el_native_time
|
/tmp/el_native_time
|
||||||
|
|
||||||
@@ -183,7 +193,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_json
|
||||||
/tmp/el_native_json
|
/tmp/el_native_json
|
||||||
|
|
||||||
@@ -193,7 +203,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_env
|
||||||
/tmp/el_native_env
|
/tmp/el_native_env
|
||||||
|
|
||||||
@@ -203,7 +213,7 @@ jobs:
|
|||||||
ELC="$(pwd)/dist/platform/elc"
|
ELC="$(pwd)/dist/platform/elc"
|
||||||
RUNTIME="$(pwd)/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 $(../scripts/el-runtime-sources.sh "$RUNTIME") \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
-lcurl -lssl -lcrypto -lpthread -lm -o /tmp/el_native_fs
|
||||||
/tmp/el_native_fs
|
/tmp/el_native_fs
|
||||||
|
|
||||||
@@ -216,10 +226,17 @@ 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/runtime/el_runtime.c dist/sdk/runtime/
|
# Ship the WHOLE runtime link set, not el_runtime.c alone. el_runtime.c
|
||||||
cp lang/runtime/el_runtime.h dist/sdk/runtime/
|
# #includes six engram headers and calls into all six sibling .c files,
|
||||||
cp lang/runtime/engram_store.c dist/sdk/runtime/
|
# so an SDK carrying only el_runtime.c{,.h} + engram_store.c{,.h} cannot
|
||||||
cp lang/runtime/engram_store.h dist/sdk/runtime/
|
# link — downstream `ld` fails on engram_ground_json, eg_find_relation,
|
||||||
|
# cog_assert_two_axis and friends. lang/runtime/SOURCES is the source of
|
||||||
|
# truth; --check makes a missing file fail the release loudly.
|
||||||
|
for f in $(scripts/el-runtime-sources.sh --check) \
|
||||||
|
$(scripts/el-runtime-sources.sh --headers --check); do
|
||||||
|
cp "lang/runtime/${f}" dist/sdk/runtime/
|
||||||
|
done
|
||||||
|
cp lang/runtime/SOURCES 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,12 +291,16 @@ jobs:
|
|||||||
"${GITEA_API}/repos/${REPO}/releases/${RELEASE_ID}/assets"
|
"${GITEA_API}/repos/${REPO}/releases/${RELEASE_ID}/assets"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Per-file assets (downstream CI needs these individually)
|
# Per-file assets (downstream CI needs these individually).
|
||||||
|
# lang/install.sh downloads every one of these by name — the list is
|
||||||
|
# lang/runtime/SOURCES. Shipping el_runtime.c alone produced a lib/
|
||||||
|
# that could not link; that is the bug this loop closes.
|
||||||
upload_asset lang/dist/platform/elc elc
|
upload_asset lang/dist/platform/elc elc
|
||||||
upload_asset lang/runtime/el_runtime.c el_runtime.c
|
for f in $(scripts/el-runtime-sources.sh --check) \
|
||||||
upload_asset lang/runtime/el_runtime.h el_runtime.h
|
$(scripts/el-runtime-sources.sh --headers --check); do
|
||||||
upload_asset lang/runtime/engram_store.c engram_store.c
|
upload_asset "lang/runtime/${f}" "${f}"
|
||||||
upload_asset lang/runtime/engram_store.h engram_store.h
|
done
|
||||||
|
upload_asset lang/runtime/SOURCES SOURCES
|
||||||
|
|
||||||
# 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
|
||||||
@@ -350,6 +371,26 @@ jobs:
|
|||||||
--version="${VERSION}" \
|
--version="${VERSION}" \
|
||||||
--source=runtime/el_runtime.js
|
--source=runtime/el_runtime.js
|
||||||
|
|
||||||
|
# el-runtime-src — the COMPLETE runtime link set as one tarball.
|
||||||
|
#
|
||||||
|
# The el-runtime-c / el-runtime-h packages above are single files and are
|
||||||
|
# kept for backward compatibility with consumers that already pull them,
|
||||||
|
# but they are NOT sufficient to link: el_runtime.c calls into six engram
|
||||||
|
# sibling translation units. New consumers should pull el-runtime-src and
|
||||||
|
# link everything named in its SOURCES file.
|
||||||
|
tar -czf /tmp/el-runtime-src.tar.gz \
|
||||||
|
-C runtime SOURCES \
|
||||||
|
$(../scripts/el-runtime-sources.sh --check) \
|
||||||
|
$(../scripts/el-runtime-sources.sh --headers --check)
|
||||||
|
|
||||||
|
gcloud artifacts generic upload \
|
||||||
|
--repository=foundation-prod \
|
||||||
|
--location=us-central1 \
|
||||||
|
--project=neuron-785695 \
|
||||||
|
--package=el-runtime-src \
|
||||||
|
--version="${VERSION}" \
|
||||||
|
--source=/tmp/el-runtime-src.tar.gz
|
||||||
|
|
||||||
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
|
||||||
# (deleted in that step after docker push)
|
# (deleted in that step after docker push)
|
||||||
@@ -386,8 +427,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 runtime/el_runtime.c /opt/el/runtime/el_runtime.c
|
# Whole runtime link set — el_runtime.c alone does not link (it calls
|
||||||
COPY runtime/el_runtime.h /opt/el/runtime/el_runtime.h
|
# into the six engram sibling TUs). See lang/runtime/SOURCES.
|
||||||
|
COPY runtime/ /opt/el/runtime/
|
||||||
COPY runtime/el_runtime.js /opt/el/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
|
||||||
|
|||||||
+42
-3
@@ -9,13 +9,52 @@ LANG_DIR="$ROOT/lang"
|
|||||||
RUNTIME="$LANG_DIR/runtime"
|
RUNTIME="$LANG_DIR/runtime"
|
||||||
ELC="$LANG_DIR/dist/platform/elc"
|
ELC="$LANG_DIR/dist/platform/elc"
|
||||||
|
|
||||||
|
# Runtime guards — catch drift and growth before they are committed, not in CI.
|
||||||
|
# check-single-runtime.sh : el_runtime.c must not be FORKED (a lagging copy
|
||||||
|
# shipped to prod and dropped learned hebb edges).
|
||||||
|
# check-runtime-growth.sh : el_runtime.c must not GROW (it is a 2026-05-03
|
||||||
|
# build shim that was never retired; see BUDGET).
|
||||||
|
echo "→ Runtime guards..."
|
||||||
|
bash "$ROOT/scripts/check-single-runtime.sh"
|
||||||
|
bash "$ROOT/scripts/check-runtime-growth.sh"
|
||||||
|
|
||||||
# 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 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"
|
echo " Build it first: see 'Rebuilding the Compiler' in lang/AGENTS.md"
|
||||||
|
echo " (link \$($ROOT/scripts/el-runtime-sources.sh $RUNTIME) — NOT el_runtime.c alone)"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# The runtime is MULTI-FILE (lang/runtime/SOURCES). This hook used to link
|
||||||
|
# "$RUNTIME/el_runtime.c" alone with stderr sent to /dev/null — so once
|
||||||
|
# el_runtime.c started calling into the engram siblings, every native test
|
||||||
|
# reported as FAILED with the real `ld` error invisible. Build the whole set
|
||||||
|
# once into an archive, then link each test against it.
|
||||||
|
|
||||||
|
# macOS: Homebrew openssl@3 is not on the default include/lib search path, so
|
||||||
|
# without these the link fails on -lssl/-lcrypto. Empty on Linux/CI.
|
||||||
|
SSL_INC=""
|
||||||
|
SSL_LIB=""
|
||||||
|
if command -v brew >/dev/null 2>&1 && OSSL="$(brew --prefix openssl@3 2>/dev/null)" && [ -n "$OSSL" ]; then
|
||||||
|
SSL_INC="-I$OSSL/include"
|
||||||
|
SSL_LIB="-L$OSSL/lib"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "→ Building runtime (compile-once, link-many)..."
|
||||||
|
HOOK_LIB="/tmp/el_hook_libel.a"
|
||||||
|
HOOK_OBJ="/tmp/el_hook_obj"
|
||||||
|
rm -rf "$HOOK_OBJ" && mkdir -p "$HOOK_OBJ"
|
||||||
|
if ! for src in $("$ROOT/scripts/el-runtime-sources.sh" --check "$RUNTIME"); do
|
||||||
|
gcc -O2 -c -I "$RUNTIME" $SSL_INC "$src" -o "$HOOK_OBJ/$(basename "${src%.c}").o" || exit 1
|
||||||
|
done; then
|
||||||
|
echo "✗ Pre-commit failed: the runtime does not compile."
|
||||||
|
echo " Re-run without 2>/dev/null to see the error:"
|
||||||
|
echo " gcc -O2 -c -I $RUNTIME \$($ROOT/scripts/el-runtime-sources.sh $RUNTIME)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ar rcs "$HOOK_LIB" "$HOOK_OBJ"/*.o
|
||||||
|
|
||||||
echo "→ Running El native tests..."
|
echo "→ Running El native tests..."
|
||||||
PASS=0
|
PASS=0
|
||||||
FAIL=0
|
FAIL=0
|
||||||
@@ -27,8 +66,8 @@ for test_file in "$LANG_DIR"/tests/native/test_*.el; do
|
|||||||
tmp_bin="/tmp/el_hook_${name}"
|
tmp_bin="/tmp/el_hook_${name}"
|
||||||
|
|
||||||
if "$ELC" --test "$test_file" > "$tmp_c" 2>/dev/null \
|
if "$ELC" --test "$test_file" > "$tmp_c" 2>/dev/null \
|
||||||
&& gcc -O2 -I "$RUNTIME" "$tmp_c" "$RUNTIME/el_runtime.c" \
|
&& gcc -O2 -I "$RUNTIME" $SSL_INC $SSL_LIB "$tmp_c" "$HOOK_LIB" \
|
||||||
-lcurl -lpthread -lm -o "$tmp_bin" 2>/dev/null \
|
-lcurl -lssl -lcrypto -lpthread -lm -o "$tmp_bin" 2>/dev/null \
|
||||||
&& "$tmp_bin" 2>/dev/null; then
|
&& "$tmp_bin" 2>/dev/null; then
|
||||||
PASS=$((PASS + 1))
|
PASS=$((PASS + 1))
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -199,21 +199,35 @@ wrong, say so with a measurement rather than editing it.
|
|||||||
|
|
||||||
All build/test commands run from `lang/` unless noted. Grounded in `.gitea/workflows/sdk-release.yaml`, `lang/install.sh`, and `lang/AGENTS.md`.
|
All build/test commands run from `lang/` unless noted. Grounded in `.gitea/workflows/sdk-release.yaml`, `lang/install.sh`, and `lang/AGENTS.md`.
|
||||||
|
|
||||||
|
> ### The runtime is MULTI-FILE — never link `el_runtime.c` alone
|
||||||
|
>
|
||||||
|
> `lang/runtime/el_runtime.c` `#include`s six engram headers and makes hard cross-TU calls into all six sibling `.c` files. **Linking it by itself fails at `ld`** (undefined `engram_ground_json`, `engram_activate_inner`, `eg_find_relation`, `cog_assert_two_axis`, …). The canonical link set lives in exactly one place — **`lang/runtime/SOURCES`** — and is printed by `scripts/el-runtime-sources.sh`:
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> scripts/el-runtime-sources.sh lang/runtime # ten .c files, in link order
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Use `$(scripts/el-runtime-sources.sh <runtime-dir>)` in every link line. Do not spell the list out longhand — it was written out in ~8 places, every copy drifted, and that is why the one-file link line below shipped broken for months. *(Corrected 2026-08-16.)*
|
||||||
|
|
||||||
**Self-host the compiler** (seed binary → gen2 elc):
|
**Self-host the compiler** (seed binary → gen2 elc):
|
||||||
```bash
|
```bash
|
||||||
cd lang
|
cd lang
|
||||||
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c # seed is the committed linux-amd64 binary
|
dist/platform/elc-linux-amd64 elc-cli.el > dist/elc-gen2.c # seed is the committed linux-amd64 binary
|
||||||
gcc -O2 -I el-compiler/runtime dist/elc-gen2.c \
|
gcc -O2 -I runtime dist/elc-gen2.c \
|
||||||
el-compiler/runtime/el_runtime.c \
|
$(../scripts/el-runtime-sources.sh runtime) \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm \
|
-lcurl -lssl -lcrypto -lpthread -lm \
|
||||||
-o dist/platform/elc
|
-o dist/platform/elc
|
||||||
```
|
```
|
||||||
On macOS/arm64 the canonical local binary is `dist/platform/elc`; verify self-hosting by recompiling and `diff`ing the emitted `.c` (see `lang/AGENTS.md`). Note: `lang/AGENTS.md` says `el_seed.c` supersedes `el_runtime.c`, but the release workflow still links `el_runtime.c`/`.h` — treat `el_runtime.c` as the published runtime; reconcile which is canonical **(verify)**.
|
On macOS/arm64 the canonical local binary is `dist/platform/elc`; verify self-hosting by recompiling and `diff`ing the emitted `.c` (see `lang/AGENTS.md`).
|
||||||
|
|
||||||
|
*(Corrected 2026-08-16: this recipe compiled `el-compiler/runtime/el_runtime.c`. That path is a **lagging fork** — the "DO NOT EDIT" list at the top of this file names it as such. Building the canonical compiler from a known-stale fork was a live defect. It now uses `lang/runtime/`, the canonical source.)*
|
||||||
|
|
||||||
|
**Which runtime file is canonical — resolved.** *(This note previously read "`lang/AGENTS.md` says `el_seed.c` supersedes `el_runtime.c`, but the release workflow still links `el_runtime.c`/`.h` — reconcile which is canonical **(verify)**." It is now reconciled.)* **Neither supersedes the other; both ship, together with eight more.** `el_runtime.c` was created on 2026-05-03 as an explicitly temporary build shim — deleted that afternoon, restored 25 minutes later "UNTIL the compiler is updated to emit `#include el_seed.h`" — and the `until` never happened, so it grew to 20.5k lines. The end state remains a seed-only boundary (`elc` emitting `#include "el_seed.h"`, `elb` dropping its hardcoded runtime path); until that lands, **the canonical unit is the set in `lang/runtime/SOURCES`, not any one file.**
|
||||||
|
|
||||||
**Build `elb`** (build coordinator, the `.NET`-style incremental linker — compiles each module independently, no monolithic blobs):
|
**Build `elb`** (build coordinator, the `.NET`-style incremental linker — compiles each module independently, no monolithic blobs):
|
||||||
```bash
|
```bash
|
||||||
dist/platform/elc elb.el > dist/elb.c
|
dist/platform/elc elb.el > dist/elb.c
|
||||||
gcc -O2 -I el-compiler/runtime dist/elb.c el-compiler/runtime/el_runtime.c \
|
gcc -O2 -I runtime dist/elb.c $(../scripts/el-runtime-sources.sh runtime) \
|
||||||
-lcurl -lssl -lcrypto -lpthread -lm -o dist/bin/elb
|
-lcurl -lssl -lcrypto -lpthread -lm -o dist/bin/elb
|
||||||
```
|
```
|
||||||
`epm` and `el-install` are then built via `elb --clean --elc=… --runtime=… --out=…`.
|
`epm` and `el-install` are then built via `elb --clean --elc=… --runtime=… --out=…`.
|
||||||
@@ -221,10 +235,16 @@ gcc -O2 -I el-compiler/runtime dist/elb.c el-compiler/runtime/el_runtime.c \
|
|||||||
**Compile + run an El program:**
|
**Compile + run an El program:**
|
||||||
```bash
|
```bash
|
||||||
elc src/app.el > dist/app.c
|
elc src/app.el > dist/app.c
|
||||||
cc -std=c11 -O2 -I <lib>/el_runtime -o dist/app dist/app.c <lib>/el_runtime.c -lcurl -lpthread
|
cc -std=c11 -O2 -I <lib> -o dist/app dist/app.c \
|
||||||
|
<lib>/el_runtime.c <lib>/el_seed.c \
|
||||||
|
<lib>/engram_store.c <lib>/engram_vindex.c <lib>/engram_geometry.c \
|
||||||
|
<lib>/engram_reason.c <lib>/engram_verify.c <lib>/engram_cognition.c \
|
||||||
|
<lib>/eg_cosine_batch.c <lib>/eg_cosine_batch_strategy_cpu.c \
|
||||||
|
-lcurl -lssl -lcrypto -lpthread -lm
|
||||||
```
|
```
|
||||||
|
(Inside this repo, replace the file list with `$(scripts/el-runtime-sources.sh lang/runtime)`. `install.sh` installs all of these into `<lib>`.)
|
||||||
|
|
||||||
**Tests** — shell suites `bash tests/{text,calendar,time,html_sanitizer}/run.sh` (with `ELC=$(pwd)/dist/platform/elc EL_HOME=$(pwd)`), plus native suites via `elc --test tests/native/test_*.el` (core, text, string, math, state, time, json, env, fs) compiled and run against `el_runtime.c`.
|
**Tests** — shell suites `bash tests/{text,calendar,time,html_sanitizer}/run.sh` (with `ELC=$(pwd)/dist/platform/elc EL_HOME=$(pwd)`), plus native suites via `elc --test tests/native/test_*.el` (core, text, string, math, state, time, json, env, fs) compiled and run against the full runtime set.
|
||||||
|
|
||||||
**Publishing — how downstream gets the SDK.** On push to `main`, `sdk-release.yaml`:
|
**Publishing — how downstream gets the SDK.** On push to `main`, `sdk-release.yaml`:
|
||||||
1. Publishes a Gitea `latest` release with per-file assets `elc`, `el_runtime.c`, `el_runtime.h`, the SDK tarball, and `el-install`.
|
1. Publishes a Gitea `latest` release with per-file assets `elc`, `el_runtime.c`, `el_runtime.h`, the SDK tarball, and `el-install`.
|
||||||
|
|||||||
@@ -548,9 +548,13 @@ before `main` does anything. That is the dividend of discovery-precedes-executio
|
|||||||
|
|
||||||
```
|
```
|
||||||
# once, ever (or when the runtime/framework changes):
|
# once, ever (or when the runtime/framework changes):
|
||||||
cc -c el_runtime.c -o el_runtime.o
|
# The runtime is MULTI-FILE — compile every .c named in lang/runtime/SOURCES.
|
||||||
elc eltest.el > eltest.c && cc -c eltest.c -o eltest.o
|
# Linking el_runtime.c alone fails: it calls into the six engram sibling TUs.
|
||||||
ar rcs libeltest.a el_runtime.o eltest.o
|
for src in $(scripts/el-runtime-sources.sh lang/runtime); do
|
||||||
|
cc -c "$src" -o "obj/$(basename "${src%.c}").o"
|
||||||
|
done
|
||||||
|
elc eltest.el > eltest.c && cc -c eltest.c -o obj/eltest.o
|
||||||
|
ar rcs libeltest.a obj/*.o
|
||||||
|
|
||||||
# per suite:
|
# per suite:
|
||||||
elc --test foo_test.el > foo_test.c # registry + bodies only
|
elc --test foo_test.el > foo_test.c # registry + bodies only
|
||||||
|
|||||||
+212
-13
@@ -23,16 +23,26 @@
|
|||||||
// warning. The runtime takes an exclusive flock at startup and a second start
|
// warning. The runtime takes an exclusive flock at startup and a second start
|
||||||
// is refused loudly with the holder's pid.
|
// is refused loudly with the holder's pid.
|
||||||
//
|
//
|
||||||
// NOT declared here, on purpose: ENGRAM_DATA_DIR. Its resolution is owned by
|
// guards: names WHAT the singleton protects — this program's data directory. The
|
||||||
// engram_resolve_data_dir() (el_runtime.c), which defaults to $HOME/.neuron/engram
|
// lock lives inside it, so the guard is keyed on the store and not on the word
|
||||||
// and fails LOUD rather than silently persisting to an ephemeral directory.
|
// "engram": two engrams against the same store cannot both run no matter how the
|
||||||
// Declaring a default for it here as well would put the data dir's fallback in
|
// environment is spelled, and two engrams against DIFFERENT stores are not each
|
||||||
// two places — which is precisely the defect this migration removes (until
|
// other's business and are not refused. Until 2026-08-16 the lock was keyed on
|
||||||
// 2026-08-15 the reseed backup path carried its own "/tmp/engram" default that
|
// the program name and $TMPDIR, and both of those sentences were false.
|
||||||
// disagreed with the resolver, so the pre-destructive safety copy landed in /tmp).
|
//
|
||||||
|
// It names the resolver rather than restating its path, for the same reason
|
||||||
|
// ENGRAM_DATA_DIR is NOT declared as an `env` entry below: engram_resolve_data_dir()
|
||||||
|
// (el_runtime.c) owns that path — it defaults to $HOME/.neuron/engram and fails
|
||||||
|
// LOUD rather than silently persisting to an ephemeral directory. Restating the
|
||||||
|
// default here would give the data dir two owners that can disagree, which is
|
||||||
|
// precisely the defect this migration removes (until 2026-08-15 the reseed backup
|
||||||
|
// path carried its own "/tmp/engram" default that disagreed with the resolver, so
|
||||||
|
// the pre-destructive safety copy landed in /tmp). A guard that resolved the path
|
||||||
|
// its own way could guard a directory the program never writes to.
|
||||||
// HOME is likewise not declared: it is a genuine environment read, not a knob.
|
// HOME is likewise not declared: it is a genuine environment read, not a knob.
|
||||||
program "engram" {
|
program "engram" {
|
||||||
singleton: "engram"
|
singleton: "engram"
|
||||||
|
guards: engram_resolve_data_dir()
|
||||||
|
|
||||||
// ── Core server ──
|
// ── Core server ──
|
||||||
env ENGRAM_BIND: String = ":8742"
|
env ENGRAM_BIND: String = ":8742"
|
||||||
@@ -1469,9 +1479,16 @@ fn route_guide_summon(method: String, path: String, body: String) -> String {
|
|||||||
//
|
//
|
||||||
// The SINGLE NODE is the DEGENERATE n=1 case of this SAME operation — not a
|
// The SINGLE NODE is the DEGENERATE n=1 case of this SAME operation — not a
|
||||||
// separate CRUD path:
|
// separate CRUD path:
|
||||||
// write(content) = reframe(region=∅, manifold=[1 node]) (route_write)
|
// write(signal) = realize(signal) → reframe(region=∅, manifold) (route_write)
|
||||||
// supersede(id,new) = reframe(region={id}, manifold=[1 node]) (route_supersede)
|
// supersede(id,new) = reframe(region={id}, manifold=[1 node]) (route_supersede)
|
||||||
// relate(a,b,rel) = the rebind sub-op in isolation (route_create_edge)
|
// relate(a,b,rel) = the rebind sub-op in isolation (route_create_edge)
|
||||||
|
//
|
||||||
|
// CORRECTED 2026-08-16: write was documented above as
|
||||||
|
// "reframe(region=∅, manifold=[1 node])", and the "[1 node]" was not the design
|
||||||
|
// — it was the DEFECT. A node is an OUTPUT of realization, never an INPUT to
|
||||||
|
// it. What arrives at an intake route is a SIGNAL, and how many nodes it
|
||||||
|
// becomes is for the realizer to say, not for the route to assume. See
|
||||||
|
// "INTAKE" below.
|
||||||
// The ONLY anti-pattern is decomposing a region-scale change into a LOOP of
|
// The ONLY anti-pattern is decomposing a region-scale change into a LOOP of
|
||||||
// independent top-level per-node updates. Here the region is the unit: one
|
// independent top-level per-node updates. Here the region is the unit: one
|
||||||
// isolate, one atomic set-replace, one persist, one verify — iterating members
|
// isolate, one atomic set-replace, one persist, one verify — iterating members
|
||||||
@@ -1686,6 +1703,174 @@ fn reframe_core(region: [String], manifold: String, reason: String, do_rebind: I
|
|||||||
",\"keystones_protected\":true}"
|
",\"keystones_protected\":true}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// INTAKE — the ONE door: signal → realization → manifold → store.
|
||||||
|
//
|
||||||
|
// THERE IS NO WRITE NODE. What arrives at an intake route is a SIGNAL. A node
|
||||||
|
// is an OUTPUT of realization, never an INPUT to it. route_write used to say:
|
||||||
|
//
|
||||||
|
// let manifold: String = "[" + body + "]" // the body IS a valid manifold node object
|
||||||
|
//
|
||||||
|
// and hand that to reframe_core. That is not a manifold — it is the request
|
||||||
|
// body wearing the word, and the comment stated the wrong assumption out loud.
|
||||||
|
// It is why a compound signal landed as ONE flat node with ZERO edges. Measured
|
||||||
|
// before this change, on a cp -Rc clone:
|
||||||
|
// POST /api/write {"type":"memory","content":"A cathedral is stone holding a
|
||||||
|
// shape that stone alone would not hold."}
|
||||||
|
// → {"ok":true,"inserted":1,"nodes_added":1,"edges_added":0,...}
|
||||||
|
// GET /api/neighbors/<new id> → [] (read back out, not taken on trust)
|
||||||
|
//
|
||||||
|
// NOTHING IS DECOMPOSED HERE, AND NOTHING MAY EVER BE. transduce(signal,
|
||||||
|
// modality) IS the realization primitive (el_runtime.c: "Manifold",
|
||||||
|
// "Realizers + transduce"). It dispatches through the dlsym realizer registry,
|
||||||
|
// so ADDING A MODALITY IS REGISTERING A REALIZER — never an edit to this file,
|
||||||
|
// and never a patch to the runtime. This function only carries what the
|
||||||
|
// primitive returns into the store, which is the one thing the engram's HTTP
|
||||||
|
// surface has never done: `grep -n 'transduce\|realize\|Manifold\|decompos'
|
||||||
|
// engram/src/server.el` returned exactly one line before this change, a comment.
|
||||||
|
//
|
||||||
|
// GENERAL BY CONSTRUCTION, NOT SPECIAL-CASED TO route_write. Five of the six
|
||||||
|
// intake doors (write, supersede, nodes, neuron/knowledge/capture,
|
||||||
|
// neuron/state-events) are the same hand-written "content string →
|
||||||
|
// engram_node_full → one flat node", differing ONLY in the node_type / tier /
|
||||||
|
// tags they hardcode. Those are parameters here, so each door can be moved onto
|
||||||
|
// this one function as it is transitioned. Only /api/write rides it in this
|
||||||
|
// pass; the rest are listed as remaining work.
|
||||||
|
//
|
||||||
|
// WHEN THERE IS NO ORGAN the signal is stored flat exactly as before, and the
|
||||||
|
// response SAYS SO ("realized":false, "organ":false). Silent flattening is the
|
||||||
|
// actual defect — a caller could not distinguish "nothing decomposed me" from
|
||||||
|
// "I decomposed into one component". el_runtime.c draws the same line at
|
||||||
|
// registration time, between an absent organ and a broken one, for the same
|
||||||
|
// reason: those two must not look alike.
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// Resolve a component KEY to the node id it was inserted as. Components are
|
||||||
|
// addressed BY KEY, never by index (el_runtime.c, "Manifold"), because the key
|
||||||
|
// is what survives persistence — so relations are resolved by key too.
|
||||||
|
fn key_to_id(keys: [String], ids: [String], key: String) -> String {
|
||||||
|
let n: Int = el_list_len(keys)
|
||||||
|
let i: Int = 0
|
||||||
|
while i < n {
|
||||||
|
if str_eq(el_list_get(keys, i), key) { return el_list_get(ids, i) }
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intake_signal(signal: String, modality: String, region: [String],
|
||||||
|
nt_in: String, tier_in: String, tags: String,
|
||||||
|
reason: String, do_rebind: Int) -> String {
|
||||||
|
let n_before: Int = engram_node_count()
|
||||||
|
let e_before: Int = engram_edge_count()
|
||||||
|
let region_n: Int = el_list_len(region)
|
||||||
|
let tomb: String = if region_n > 0 { supersede_set(region, reason) } else { "" }
|
||||||
|
|
||||||
|
// Identity can never be minted through intake — the same rule
|
||||||
|
// insert_manifold_json holds, applied at the one door instead of per-route.
|
||||||
|
let nt: String = if str_eq(nt_in, "") { "Memory" } else { nt_in }
|
||||||
|
if str_eq(nt, "self") { nt = "Memory" }
|
||||||
|
if str_eq(nt, "values") { nt = "Memory" }
|
||||||
|
let tier: String = if str_eq(tier_in, "") { "Working" } else { tier_in }
|
||||||
|
|
||||||
|
let has_organ: Int = realizer_has(modality)
|
||||||
|
let new_ids: [String] = el_list_empty()
|
||||||
|
let keys: [String] = el_list_empty()
|
||||||
|
let ncomp: Int = 0
|
||||||
|
let nrel: Int = 0
|
||||||
|
let realized: Int = 0
|
||||||
|
|
||||||
|
if has_organ > 0 {
|
||||||
|
let m: Manifold = transduce(signal, modality)
|
||||||
|
// A realizer that returns a bare Geometry transduces NOTHING by design
|
||||||
|
// (el_runtime.c) — manifold_is() is the check, so a fingerprinting organ
|
||||||
|
// is not silently mistaken for a decomposing one.
|
||||||
|
if manifold_is(m) > 0 {
|
||||||
|
realized = 1
|
||||||
|
ncomp = manifold_size(m)
|
||||||
|
let i: Int = 0
|
||||||
|
let prev: String = ""
|
||||||
|
while i < ncomp {
|
||||||
|
let key: String = manifold_key(m, i)
|
||||||
|
let role: String = manifold_role(m, i)
|
||||||
|
// The component's OWN geometry, at its own width — this is the
|
||||||
|
// whole point of a manifold over a fingerprint, and it is why
|
||||||
|
// node_attach_geometry is used rather than re-embedding the
|
||||||
|
// component's name as text.
|
||||||
|
let g: Geometry = manifold_geometry(m, i)
|
||||||
|
let ctags: String = "[\"component\",\"role:" + role + "\",\"modality:" + modality + "\"]"
|
||||||
|
let cid: String = engram_node_full(key, nt, key, 0.5, 0.5, 0.9, tier, ctags)
|
||||||
|
let landed: Int = node_attach_geometry(cid, g)
|
||||||
|
let freed: Int = geometry_free(g)
|
||||||
|
new_ids = el_list_append(new_ids, cid)
|
||||||
|
keys = el_list_append(keys, key)
|
||||||
|
// PRESERVED CONTRACT: manifold_member wires the inserted set
|
||||||
|
// into one connected sub-graph, exactly as insert_manifold_json
|
||||||
|
// already did. Not reinvented — reused.
|
||||||
|
if !str_eq(prev, "") { engram_connect(prev, cid, 0.6, "manifold_member") }
|
||||||
|
prev = cid
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
// THE RELATIONS ARE THE CONTENT. Relation weight IS the grounding
|
||||||
|
// (correspondence-and-censorship §1) — it arrives on the edge from
|
||||||
|
// the realizer and nothing here computes or second-guesses it.
|
||||||
|
nrel = manifold_rel_count(m)
|
||||||
|
let j: Int = 0
|
||||||
|
while j < nrel {
|
||||||
|
let fk: String = manifold_rel_from(m, j)
|
||||||
|
let rn: String = manifold_rel_name(m, j)
|
||||||
|
let tk: String = manifold_rel_to(m, j)
|
||||||
|
let w: Float = manifold_rel_weight(m, j)
|
||||||
|
let fid: String = key_to_id(keys, new_ids, fk)
|
||||||
|
let tid: String = key_to_id(keys, new_ids, tk)
|
||||||
|
if !str_eq(fid, "") {
|
||||||
|
if !str_eq(tid, "") {
|
||||||
|
engram_connect(fid, tid, w, rn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j = j + 1
|
||||||
|
}
|
||||||
|
let mfreed: Int = manifold_free(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NO ORGAN: store the signal flat, as before — but say so. This is the
|
||||||
|
// pre-existing behaviour preserved verbatim, not a new fallback path.
|
||||||
|
if realized == 0 {
|
||||||
|
let label: String = str_slice(signal, 0, 60)
|
||||||
|
let fid: String = engram_node_full(signal, nt, label, 0.5, 0.5, 0.9, tier, tags)
|
||||||
|
new_ids = el_list_append(new_ids, fid)
|
||||||
|
}
|
||||||
|
|
||||||
|
let inserted: Int = el_list_len(new_ids)
|
||||||
|
let bound: Int = if do_rebind > 0 { rebind_cosine(new_ids, tomb) } else { 0 }
|
||||||
|
let saved: Int = persist_canonical()
|
||||||
|
let new_csv: String = ""
|
||||||
|
let k: Int = 0
|
||||||
|
while k < inserted {
|
||||||
|
let sep: String = if k == 0 { "" } else { "," }
|
||||||
|
new_csv = new_csv + sep + "\"" + el_list_get(new_ids, k) + "\""
|
||||||
|
k = k + 1
|
||||||
|
}
|
||||||
|
let realized_s: String = if realized > 0 { "true" } else { "false" }
|
||||||
|
let organ_s: String = if has_organ > 0 { "true" } else { "false" }
|
||||||
|
return "{\"ok\":true,\"region_superseded\":" + int_to_str(region_n) +
|
||||||
|
",\"tombstone_id\":\"" + tomb + "\"" +
|
||||||
|
",\"inserted\":" + int_to_str(inserted) +
|
||||||
|
",\"new_ids\":[" + new_csv + "]" +
|
||||||
|
",\"edges_rebound\":" + int_to_str(bound) +
|
||||||
|
",\"realized\":" + realized_s +
|
||||||
|
",\"modality\":\"" + modality + "\"" +
|
||||||
|
",\"organ\":" + organ_s +
|
||||||
|
",\"components\":" + int_to_str(ncomp) +
|
||||||
|
",\"relations\":" + int_to_str(nrel) +
|
||||||
|
",\"nodes_added\":" + int_to_str(engram_node_count() - n_before) +
|
||||||
|
",\"edges_added\":" + int_to_str(engram_edge_count() - e_before) +
|
||||||
|
",\"node_count\":" + int_to_str(engram_node_count()) +
|
||||||
|
",\"edge_count\":" + int_to_str(engram_edge_count()) +
|
||||||
|
",\"keystones_protected\":true}"
|
||||||
|
}
|
||||||
|
|
||||||
// POST /api/reframe — the universal set-based mutation.
|
// POST /api/reframe — the universal set-based mutation.
|
||||||
// Body: {vantage?, region_ids?(csv), k?, expand?, manifold(json array), reason?, rebind?}
|
// Body: {vantage?, region_ids?(csv), k?, expand?, manifold(json array), reason?, rebind?}
|
||||||
// region_ids (explicit) wins; else cosine-isolate around vantage.
|
// region_ids (explicit) wins; else cosine-isolate around vantage.
|
||||||
@@ -1722,18 +1907,32 @@ fn route_reframe(method: String, path: String, body: String) -> String {
|
|||||||
return reframe_core(region, manifold, reason, do_rebind)
|
return reframe_core(region, manifold, reason, do_rebind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write — DEGENERATE n=1 of reframe: region=∅, manifold=[1 node]. The SAME
|
// write — INTAKE OF A SIGNAL. Not "reframe with a manifold of one node": the
|
||||||
// reframe_core path. rebind off so the pure-add matches plain node creation.
|
// route no longer decides how many nodes the signal is. It hands the signal to
|
||||||
// POST /api/write {content, node_type?, tier?, tags?}
|
// the realization primitive and stores whatever manifold comes back.
|
||||||
|
//
|
||||||
|
// The line this replaces was:
|
||||||
|
// let manifold: String = "[" + body + "]" // the body IS a valid manifold node object
|
||||||
|
// which asserted that a request body is a manifold. It is not, and that single
|
||||||
|
// assertion is the whole measured defect (1 node, 0 edges, [] neighbors).
|
||||||
|
//
|
||||||
|
// rebind stays off so a pure add still matches plain node creation.
|
||||||
|
// POST /api/write {content, modality?, node_type?, tier?, tags?}
|
||||||
fn route_write(method: String, path: String, body: String) -> String {
|
fn route_write(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("write: content required") }
|
if str_eq(content, "") { return err_json("write: content required") }
|
||||||
let nt: String = json_get_string(body, "node_type")
|
let nt: String = json_get_string(body, "node_type")
|
||||||
if str_eq(nt, "self") { return err_json("write: identity is write-protected") }
|
if str_eq(nt, "self") { return err_json("write: identity is write-protected") }
|
||||||
if str_eq(nt, "values") { return err_json("write: identity is write-protected") }
|
if str_eq(nt, "values") { return err_json("write: identity is write-protected") }
|
||||||
|
// The modality names which organ to sense with. It is data, never a branch:
|
||||||
|
// a new modality is a realizer_register call somewhere else in the program,
|
||||||
|
// not another endpoint and not another case here.
|
||||||
|
let mod_raw: String = json_get_string(body, "modality")
|
||||||
|
let modality: String = if str_eq(mod_raw, "") { "text" } else { mod_raw }
|
||||||
|
let tier: String = json_get_string(body, "tier")
|
||||||
|
let tags: String = json_get_raw(body, "tags")
|
||||||
let empty: [String] = el_list_empty()
|
let empty: [String] = el_list_empty()
|
||||||
let manifold: String = "[" + body + "]" // the body IS a valid manifold node object
|
return intake_signal(content, modality, empty, nt, tier, tags, "write", 0)
|
||||||
return reframe_core(empty, manifold, "write", 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// supersede — DEGENERATE n=1 of reframe: region={id}, manifold=[1 node]. The
|
// supersede — DEGENERATE n=1 of reframe: region={id}, manifold=[1 node]. The
|
||||||
|
|||||||
@@ -3,10 +3,14 @@
|
|||||||
# Throwaway HOME + /tmp only. Never touches ~/.neuron or :8742.
|
# Throwaway HOME + /tmp only. Never touches ~/.neuron or :8742.
|
||||||
set -u
|
set -u
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
RT="$HERE/../../lang/runtime/el_runtime.c"
|
RTSRC="$("$HERE/../../scripts/el-runtime-sources.sh" "$HERE/../../lang/runtime")"
|
||||||
ST="$HERE/../../lang/runtime/engram_store.c"
|
# The runtime is MULTI-FILE (lang/runtime/SOURCES). This harness used to link
|
||||||
GEO="$HERE/../../lang/runtime/engram_geometry.c"
|
# el_runtime.c + engram_store.c only, which stopped linking once el_runtime.c
|
||||||
VIDX="$HERE/../../lang/runtime/engram_vindex.c"
|
# began calling into the other engram siblings. Unquoted on purpose: a list.
|
||||||
|
SSLFLAGS=""
|
||||||
|
if command -v brew >/dev/null 2>&1 && O="$(brew --prefix openssl@3 2>/dev/null)"; then
|
||||||
|
SSLFLAGS="-I$O/include -L$O/lib"
|
||||||
|
fi
|
||||||
INC="$HERE/../../lang/runtime"
|
INC="$HERE/../../lang/runtime"
|
||||||
WORK="$(mktemp -d /tmp/engram-p0-XXXXXX)"
|
WORK="$(mktemp -d /tmp/engram-p0-XXXXXX)"
|
||||||
export HOME="$WORK/home"; mkdir -p "$HOME"
|
export HOME="$WORK/home"; mkdir -p "$HOME"
|
||||||
@@ -14,8 +18,8 @@ unset ENGRAM_STORE
|
|||||||
fail=0
|
fail=0
|
||||||
|
|
||||||
echo "== compile (plain) =="
|
echo "== compile (plain) =="
|
||||||
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p0_emb.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p0_emb.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p0" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p0" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
||||||
|
|
||||||
D="$WORK/d"; mkdir -p "$D"
|
D="$WORK/d"; mkdir -p "$D"
|
||||||
"$WORK/p0" "$D" || { echo "FAIL: run"; fail=1; }
|
"$WORK/p0" "$D" || { echo "FAIL: run"; fail=1; }
|
||||||
@@ -69,8 +73,8 @@ PY
|
|||||||
echo
|
echo
|
||||||
echo "== ASan+UBSan =="
|
echo "== ASan+UBSan =="
|
||||||
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
||||||
-I "$INC" "$HERE/test_interoception_p0_emb.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
-I "$INC" "$HERE/test_interoception_p0_emb.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p0.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -20 "$WORK/san_cc.log"; fail=1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p0.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -20 "$WORK/san_cc.log"; fail=1; }
|
||||||
if [ -x "$WORK/p0.san" ]; then
|
if [ -x "$WORK/p0.san" ]; then
|
||||||
export ASAN_OPTIONS=detect_leaks=0
|
export ASAN_OPTIONS=detect_leaks=0
|
||||||
DS="$WORK/ds"; mkdir -p "$DS"
|
DS="$WORK/ds"; mkdir -p "$DS"
|
||||||
|
|||||||
@@ -3,10 +3,14 @@
|
|||||||
# Throwaway HOME + /tmp only. Never touches ~/.neuron or :8742.
|
# Throwaway HOME + /tmp only. Never touches ~/.neuron or :8742.
|
||||||
set -u
|
set -u
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
RT="$HERE/../../lang/runtime/el_runtime.c"
|
RTSRC="$("$HERE/../../scripts/el-runtime-sources.sh" "$HERE/../../lang/runtime")"
|
||||||
ST="$HERE/../../lang/runtime/engram_store.c"
|
# The runtime is MULTI-FILE (lang/runtime/SOURCES). This harness used to link
|
||||||
GEO="$HERE/../../lang/runtime/engram_geometry.c"
|
# el_runtime.c + engram_store.c only, which stopped linking once el_runtime.c
|
||||||
VIDX="$HERE/../../lang/runtime/engram_vindex.c"
|
# began calling into the other engram siblings. Unquoted on purpose: a list.
|
||||||
|
SSLFLAGS=""
|
||||||
|
if command -v brew >/dev/null 2>&1 && O="$(brew --prefix openssl@3 2>/dev/null)"; then
|
||||||
|
SSLFLAGS="-I$O/include -L$O/lib"
|
||||||
|
fi
|
||||||
INC="$HERE/../../lang/runtime"
|
INC="$HERE/../../lang/runtime"
|
||||||
WORK="$(mktemp -d /tmp/engram-p1-XXXXXX)"
|
WORK="$(mktemp -d /tmp/engram-p1-XXXXXX)"
|
||||||
export HOME="$WORK/home"; mkdir -p "$HOME"
|
export HOME="$WORK/home"; mkdir -p "$HOME"
|
||||||
@@ -14,8 +18,8 @@ unset ENGRAM_STORE ENGRAM_CONSOLIDATION ENGRAM_CONSOL_CONN_MIN ENGRAM_CONSOL_PER
|
|||||||
fail=0
|
fail=0
|
||||||
|
|
||||||
echo "== compile =="
|
echo "== compile =="
|
||||||
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p1_consol.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p1_consol.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p1" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p1" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== (a) HEADLINE: hebb accrual curve over N co-activations (flag OFF, pure trunk) =="
|
echo "== (a) HEADLINE: hebb accrual curve over N co-activations (flag OFF, pure trunk) =="
|
||||||
@@ -129,8 +133,8 @@ cat "$WORK/off.txt" | sed 's/^/ /'
|
|||||||
echo
|
echo
|
||||||
echo "== ASan+UBSan (connect + perm + accrual-short) =="
|
echo "== ASan+UBSan (connect + perm + accrual-short) =="
|
||||||
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
||||||
-I "$INC" "$HERE/test_interoception_p1_consol.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
-I "$INC" "$HERE/test_interoception_p1_consol.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p1.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -25 "$WORK/san_cc.log"; fail=1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p1.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -25 "$WORK/san_cc.log"; fail=1; }
|
||||||
if [ -x "$WORK/p1.san" ]; then
|
if [ -x "$WORK/p1.san" ]; then
|
||||||
export ASAN_OPTIONS=detect_leaks=0
|
export ASAN_OPTIONS=detect_leaks=0
|
||||||
DS="$WORK/san"; mkdir -p "$DS"
|
DS="$WORK/san"; mkdir -p "$DS"
|
||||||
|
|||||||
@@ -3,10 +3,14 @@
|
|||||||
# Throwaway HOME + /tmp only. TC defaults to 3600s; we pin it for the math.
|
# Throwaway HOME + /tmp only. TC defaults to 3600s; we pin it for the math.
|
||||||
set -u
|
set -u
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
RT="$HERE/../../lang/runtime/el_runtime.c"
|
RTSRC="$("$HERE/../../scripts/el-runtime-sources.sh" "$HERE/../../lang/runtime")"
|
||||||
ST="$HERE/../../lang/runtime/engram_store.c"
|
# The runtime is MULTI-FILE (lang/runtime/SOURCES). This harness used to link
|
||||||
GEO="$HERE/../../lang/runtime/engram_geometry.c"
|
# el_runtime.c + engram_store.c only, which stopped linking once el_runtime.c
|
||||||
VIDX="$HERE/../../lang/runtime/engram_vindex.c"
|
# began calling into the other engram siblings. Unquoted on purpose: a list.
|
||||||
|
SSLFLAGS=""
|
||||||
|
if command -v brew >/dev/null 2>&1 && O="$(brew --prefix openssl@3 2>/dev/null)"; then
|
||||||
|
SSLFLAGS="-I$O/include -L$O/lib"
|
||||||
|
fi
|
||||||
INC="$HERE/../../lang/runtime"
|
INC="$HERE/../../lang/runtime"
|
||||||
WORK="$(mktemp -d /tmp/engram-p2-XXXXXX)"
|
WORK="$(mktemp -d /tmp/engram-p2-XXXXXX)"
|
||||||
export HOME="$WORK/home"; mkdir -p "$HOME"
|
export HOME="$WORK/home"; mkdir -p "$HOME"
|
||||||
@@ -15,8 +19,8 @@ unset ENGRAM_STORE
|
|||||||
fail=0
|
fail=0
|
||||||
|
|
||||||
echo "== compile =="
|
echo "== compile =="
|
||||||
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p2_chrono.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p2_chrono.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p2" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p2" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
||||||
|
|
||||||
sum_wm(){ python3 -c "import json,sys; g=json.load(open('$1')); print(sum(n.get('working_memory_weight',0) for n in g['nodes']))"; }
|
sum_wm(){ python3 -c "import json,sys; g=json.load(open('$1')); print(sum(n.get('working_memory_weight',0) for n in g['nodes']))"; }
|
||||||
|
|
||||||
@@ -78,8 +82,8 @@ python3 -c "import sys; sys.exit(0 if abs($OFFWM-1.2)<1e-9 else 1)" \
|
|||||||
echo
|
echo
|
||||||
echo "== ASan+UBSan =="
|
echo "== ASan+UBSan =="
|
||||||
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
||||||
-I "$INC" "$HERE/test_interoception_p2_chrono.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
-I "$INC" "$HERE/test_interoception_p2_chrono.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p2.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -25 "$WORK/san_cc.log"; fail=1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p2.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -25 "$WORK/san_cc.log"; fail=1; }
|
||||||
if [ -x "$WORK/p2.san" ]; then
|
if [ -x "$WORK/p2.san" ]; then
|
||||||
export ASAN_OPTIONS=detect_leaks=0
|
export ASAN_OPTIONS=detect_leaks=0
|
||||||
DS="$WORK/san"; mkdir -p "$DS"
|
DS="$WORK/san"; mkdir -p "$DS"
|
||||||
|
|||||||
@@ -3,18 +3,22 @@
|
|||||||
# Read-only pure primitive; no store, no flag. Throwaway /tmp only.
|
# Read-only pure primitive; no store, no flag. Throwaway /tmp only.
|
||||||
set -u
|
set -u
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
RT="$HERE/../../lang/runtime/el_runtime.c"
|
RTSRC="$("$HERE/../../scripts/el-runtime-sources.sh" "$HERE/../../lang/runtime")"
|
||||||
ST="$HERE/../../lang/runtime/engram_store.c"
|
# The runtime is MULTI-FILE (lang/runtime/SOURCES). This harness used to link
|
||||||
GEO="$HERE/../../lang/runtime/engram_geometry.c"
|
# el_runtime.c + engram_store.c only, which stopped linking once el_runtime.c
|
||||||
VIDX="$HERE/../../lang/runtime/engram_vindex.c"
|
# began calling into the other engram siblings. Unquoted on purpose: a list.
|
||||||
|
SSLFLAGS=""
|
||||||
|
if command -v brew >/dev/null 2>&1 && O="$(brew --prefix openssl@3 2>/dev/null)"; then
|
||||||
|
SSLFLAGS="-I$O/include -L$O/lib"
|
||||||
|
fi
|
||||||
INC="$HERE/../../lang/runtime"
|
INC="$HERE/../../lang/runtime"
|
||||||
WORK="$(mktemp -d /tmp/engram-p3-XXXXXX)"
|
WORK="$(mktemp -d /tmp/engram-p3-XXXXXX)"
|
||||||
export HOME="$WORK/home"; mkdir -p "$HOME"
|
export HOME="$WORK/home"; mkdir -p "$HOME"
|
||||||
fail=0
|
fail=0
|
||||||
|
|
||||||
echo "== compile =="
|
echo "== compile =="
|
||||||
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p3_drift.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p3_drift.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p3" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p3" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
||||||
|
|
||||||
"$WORK/p3" > "$WORK/out.txt" 2>&1 || { echo "FAIL run"; cat "$WORK/out.txt"; fail=1; }
|
"$WORK/p3" > "$WORK/out.txt" 2>&1 || { echo "FAIL run"; cat "$WORK/out.txt"; fail=1; }
|
||||||
cat "$WORK/out.txt" | sed 's/^/ /'
|
cat "$WORK/out.txt" | sed 's/^/ /'
|
||||||
@@ -52,8 +56,8 @@ PY
|
|||||||
echo
|
echo
|
||||||
echo "== ASan+UBSan =="
|
echo "== ASan+UBSan =="
|
||||||
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
||||||
-I "$INC" "$HERE/test_interoception_p3_drift.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
-I "$INC" "$HERE/test_interoception_p3_drift.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p3.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -25 "$WORK/san_cc.log"; fail=1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p3.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -25 "$WORK/san_cc.log"; fail=1; }
|
||||||
if [ -x "$WORK/p3.san" ]; then
|
if [ -x "$WORK/p3.san" ]; then
|
||||||
export ASAN_OPTIONS=detect_leaks=0
|
export ASAN_OPTIONS=detect_leaks=0
|
||||||
"$WORK/p3.san" >/dev/null 2>"$WORK/san.log"
|
"$WORK/p3.san" >/dev/null 2>"$WORK/san.log"
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
# M-INTEROCEPTION P4 gate: afferent input counters in act-stats (additive).
|
# M-INTEROCEPTION P4 gate: afferent input counters in act-stats (additive).
|
||||||
set -u
|
set -u
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
RT="$HERE/../../lang/runtime/el_runtime.c"
|
RTSRC="$("$HERE/../../scripts/el-runtime-sources.sh" "$HERE/../../lang/runtime")"
|
||||||
ST="$HERE/../../lang/runtime/engram_store.c"
|
# The runtime is MULTI-FILE (lang/runtime/SOURCES). This harness used to link
|
||||||
GEO="$HERE/../../lang/runtime/engram_geometry.c"
|
# el_runtime.c + engram_store.c only, which stopped linking once el_runtime.c
|
||||||
VIDX="$HERE/../../lang/runtime/engram_vindex.c"
|
# began calling into the other engram siblings. Unquoted on purpose: a list.
|
||||||
|
SSLFLAGS=""
|
||||||
|
if command -v brew >/dev/null 2>&1 && O="$(brew --prefix openssl@3 2>/dev/null)"; then
|
||||||
|
SSLFLAGS="-I$O/include -L$O/lib"
|
||||||
|
fi
|
||||||
INC="$HERE/../../lang/runtime"
|
INC="$HERE/../../lang/runtime"
|
||||||
WORK="$(mktemp -d /tmp/engram-p4-XXXXXX)"
|
WORK="$(mktemp -d /tmp/engram-p4-XXXXXX)"
|
||||||
export HOME="$WORK/home"; mkdir -p "$HOME"
|
export HOME="$WORK/home"; mkdir -p "$HOME"
|
||||||
@@ -13,8 +17,8 @@ unset ENGRAM_STORE
|
|||||||
fail=0
|
fail=0
|
||||||
|
|
||||||
echo "== compile =="
|
echo "== compile =="
|
||||||
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p4_afferent.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p4_afferent.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p4" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p4" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
||||||
|
|
||||||
"$WORK/p4" > "$WORK/out.txt" 2>&1 || { echo "FAIL run"; cat "$WORK/out.txt"; fail=1; }
|
"$WORK/p4" > "$WORK/out.txt" 2>&1 || { echo "FAIL run"; cat "$WORK/out.txt"; fail=1; }
|
||||||
grep -oE 'aff_[a-z_]+":[0-9]+' "$WORK/out.txt" | sed 's/^/ /' | head -30
|
grep -oE 'aff_[a-z_]+":[0-9]+' "$WORK/out.txt" | sed 's/^/ /' | head -30
|
||||||
@@ -53,8 +57,8 @@ PY
|
|||||||
echo
|
echo
|
||||||
echo "== ASan+UBSan =="
|
echo "== ASan+UBSan =="
|
||||||
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
||||||
-I "$INC" "$HERE/test_interoception_p4_afferent.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
-I "$INC" "$HERE/test_interoception_p4_afferent.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p4.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -25 "$WORK/san_cc.log"; fail=1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p4.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -25 "$WORK/san_cc.log"; fail=1; }
|
||||||
if [ -x "$WORK/p4.san" ]; then
|
if [ -x "$WORK/p4.san" ]; then
|
||||||
export ASAN_OPTIONS=detect_leaks=0
|
export ASAN_OPTIONS=detect_leaks=0
|
||||||
"$WORK/p4.san" >/dev/null 2>"$WORK/san.log"
|
"$WORK/p4.san" >/dev/null 2>"$WORK/san.log"
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
# M-INTEROCEPTION P5 gate: dream-recall builtin engram_dreams_json (honesty rail).
|
# M-INTEROCEPTION P5 gate: dream-recall builtin engram_dreams_json (honesty rail).
|
||||||
set -u
|
set -u
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
RT="$HERE/../../lang/runtime/el_runtime.c"
|
RTSRC="$("$HERE/../../scripts/el-runtime-sources.sh" "$HERE/../../lang/runtime")"
|
||||||
ST="$HERE/../../lang/runtime/engram_store.c"
|
# The runtime is MULTI-FILE (lang/runtime/SOURCES). This harness used to link
|
||||||
GEO="$HERE/../../lang/runtime/engram_geometry.c"
|
# el_runtime.c + engram_store.c only, which stopped linking once el_runtime.c
|
||||||
VIDX="$HERE/../../lang/runtime/engram_vindex.c"
|
# began calling into the other engram siblings. Unquoted on purpose: a list.
|
||||||
|
SSLFLAGS=""
|
||||||
|
if command -v brew >/dev/null 2>&1 && O="$(brew --prefix openssl@3 2>/dev/null)"; then
|
||||||
|
SSLFLAGS="-I$O/include -L$O/lib"
|
||||||
|
fi
|
||||||
INC="$HERE/../../lang/runtime"
|
INC="$HERE/../../lang/runtime"
|
||||||
WORK="$(mktemp -d /tmp/engram-p5-XXXXXX)"
|
WORK="$(mktemp -d /tmp/engram-p5-XXXXXX)"
|
||||||
export HOME="$WORK/home"; mkdir -p "$HOME"
|
export HOME="$WORK/home"; mkdir -p "$HOME"
|
||||||
@@ -13,8 +17,8 @@ unset ENGRAM_STORE
|
|||||||
fail=0
|
fail=0
|
||||||
|
|
||||||
echo "== compile =="
|
echo "== compile =="
|
||||||
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p5_dreams.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p5_dreams.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p5" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p5" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
|
||||||
|
|
||||||
D="$WORK/d"; mkdir -p "$D"
|
D="$WORK/d"; mkdir -p "$D"
|
||||||
"$WORK/p5" "$D" > "$WORK/out.txt" 2>&1 || { echo "FAIL run"; cat "$WORK/out.txt"; fail=1; }
|
"$WORK/p5" "$D" > "$WORK/out.txt" 2>&1 || { echo "FAIL run"; cat "$WORK/out.txt"; fail=1; }
|
||||||
@@ -57,8 +61,8 @@ PY
|
|||||||
echo
|
echo
|
||||||
echo "== ASan+UBSan =="
|
echo "== ASan+UBSan =="
|
||||||
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
||||||
-I "$INC" "$HERE/test_interoception_p5_dreams.c" "$RT" "$ST" "$GEO" "$VIDX" \
|
-I "$INC" "$HERE/test_interoception_p5_dreams.c" $RTSRC $SSLFLAGS \
|
||||||
-lcurl -lm -o "$WORK/p5.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -25 "$WORK/san_cc.log"; fail=1; }
|
-lcurl -lssl -lcrypto -lpthread -lm -lm -o "$WORK/p5.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -25 "$WORK/san_cc.log"; fail=1; }
|
||||||
if [ -x "$WORK/p5.san" ]; then
|
if [ -x "$WORK/p5.san" ]; then
|
||||||
export ASAN_OPTIONS=detect_leaks=0
|
export ASAN_OPTIONS=detect_leaks=0
|
||||||
DS="$WORK/ds"; mkdir -p "$DS"
|
DS="$WORK/ds"; mkdir -p "$DS"
|
||||||
|
|||||||
@@ -6,8 +6,14 @@
|
|||||||
# Writes ONLY under a throwaway /tmp dir with a throwaway HOME.
|
# Writes ONLY under a throwaway /tmp dir with a throwaway HOME.
|
||||||
set -u
|
set -u
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
RT="$HERE/../../lang/runtime/el_runtime.c"
|
RTSRC="$("$HERE/../../scripts/el-runtime-sources.sh" "$HERE/../../lang/runtime")"
|
||||||
ST="$HERE/../../lang/runtime/engram_store.c"
|
# The runtime is MULTI-FILE (lang/runtime/SOURCES). This harness used to link
|
||||||
|
# el_runtime.c + engram_store.c only, which stopped linking once el_runtime.c
|
||||||
|
# began calling into the other engram siblings. Unquoted on purpose: a list.
|
||||||
|
SSLFLAGS=""
|
||||||
|
if command -v brew >/dev/null 2>&1 && O="$(brew --prefix openssl@3 2>/dev/null)"; then
|
||||||
|
SSLFLAGS="-I$O/include -L$O/lib"
|
||||||
|
fi
|
||||||
INC="$HERE/../../lang/runtime"
|
INC="$HERE/../../lang/runtime"
|
||||||
WORK="$(mktemp -d /tmp/engram-m35-XXXXXX)"
|
WORK="$(mktemp -d /tmp/engram-m35-XXXXXX)"
|
||||||
BIN="$WORK/m35"
|
BIN="$WORK/m35"
|
||||||
@@ -17,7 +23,7 @@ unset ENGRAM_STORE
|
|||||||
fail=0
|
fail=0
|
||||||
|
|
||||||
echo "== compiling harness (gcc: el_runtime.c + engram_store.c + test_m35_hebb_persist.c) =="
|
echo "== compiling harness (gcc: el_runtime.c + engram_store.c + test_m35_hebb_persist.c) =="
|
||||||
gcc -O1 -std=c11 -I "$INC" "$HERE/test_m35_hebb_persist.c" "$RT" "$ST" -lcurl -o "$BIN" 2>"$WORK/cc.log"
|
gcc -O1 -std=c11 -I "$INC" "$HERE/test_m35_hebb_persist.c" $RTSRC $SSLFLAGS -lcurl -lssl -lcrypto -lpthread -lm -o "$BIN" 2>"$WORK/cc.log"
|
||||||
if [ $? -ne 0 ]; then echo "COMPILE FAILED:"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; fi
|
if [ $? -ne 0 ]; then echo "COMPILE FAILED:"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
@@ -139,7 +145,7 @@ echo
|
|||||||
echo "== 5) ASan+UBSan build, exercise the full persist+reboot flow (leaks off — harness intentionally leaks el_strdup) =="
|
echo "== 5) ASan+UBSan build, exercise the full persist+reboot flow (leaks off — harness intentionally leaks el_strdup) =="
|
||||||
SANBIN="$WORK/m35.san"
|
SANBIN="$WORK/m35.san"
|
||||||
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
||||||
-I "$INC" "$HERE/test_m35_hebb_persist.c" "$RT" "$ST" -lcurl -o "$SANBIN" 2>"$WORK/san_cc.log"
|
-I "$INC" "$HERE/test_m35_hebb_persist.c" $RTSRC $SSLFLAGS -lcurl -lssl -lcrypto -lpthread -lm -o "$SANBIN" 2>"$WORK/san_cc.log"
|
||||||
if [ $? -ne 0 ]; then echo " SAN COMPILE FAILED:"; tail -20 "$WORK/san_cc.log"; fail=1; else
|
if [ $? -ne 0 ]; then echo " SAN COMPILE FAILED:"; tail -20 "$WORK/san_cc.log"; fail=1; else
|
||||||
export ASAN_OPTIONS=detect_leaks=0
|
export ASAN_OPTIONS=detect_leaks=0
|
||||||
DSAN="$WORK/san"; mkdir -p "$DSAN"
|
DSAN="$WORK/san"; mkdir -p "$DSAN"
|
||||||
|
|||||||
@@ -4,8 +4,14 @@
|
|||||||
# Writes ONLY under a throwaway /tmp dir with a throwaway HOME + ENGRAM_DATA_DIR.
|
# Writes ONLY under a throwaway /tmp dir with a throwaway HOME + ENGRAM_DATA_DIR.
|
||||||
set -u
|
set -u
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
RT="$HERE/../../lang/runtime/el_runtime.c"
|
RTSRC="$("$HERE/../../scripts/el-runtime-sources.sh" "$HERE/../../lang/runtime")"
|
||||||
ST="$HERE/../../lang/runtime/engram_store.c"
|
# The runtime is MULTI-FILE (lang/runtime/SOURCES). This harness used to link
|
||||||
|
# el_runtime.c + engram_store.c only, which stopped linking once el_runtime.c
|
||||||
|
# began calling into the other engram siblings. Unquoted on purpose: a list.
|
||||||
|
SSLFLAGS=""
|
||||||
|
if command -v brew >/dev/null 2>&1 && O="$(brew --prefix openssl@3 2>/dev/null)"; then
|
||||||
|
SSLFLAGS="-I$O/include -L$O/lib"
|
||||||
|
fi
|
||||||
INC="$HERE/../../lang/runtime"
|
INC="$HERE/../../lang/runtime"
|
||||||
WORK="$(mktemp -d /tmp/engram-m3-XXXXXX)"
|
WORK="$(mktemp -d /tmp/engram-m3-XXXXXX)"
|
||||||
DATA="$WORK/data"; mkdir -p "$DATA"
|
DATA="$WORK/data"; mkdir -p "$DATA"
|
||||||
@@ -17,7 +23,7 @@ unset ENGRAM_STORE
|
|||||||
fail=0
|
fail=0
|
||||||
|
|
||||||
echo "== compiling harness (gcc: el_runtime.c + engram_store.c + test_m3_parity.c) =="
|
echo "== compiling harness (gcc: el_runtime.c + engram_store.c + test_m3_parity.c) =="
|
||||||
gcc -O1 -std=c11 -I "$INC" "$HERE/test_m3_parity.c" "$RT" "$ST" -lcurl -o "$BIN" 2>"$WORK/cc.log"
|
gcc -O1 -std=c11 -I "$INC" "$HERE/test_m3_parity.c" $RTSRC $SSLFLAGS -lcurl -lssl -lcrypto -lpthread -lm -o "$BIN" 2>"$WORK/cc.log"
|
||||||
if [ $? -ne 0 ]; then echo "COMPILE FAILED:"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; fi
|
if [ $? -ne 0 ]; then echo "COMPILE FAILED:"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; fi
|
||||||
grep -i warning "$WORK/cc.log" | grep -iE 'engram_store|eg_store|eg_load|scan_nodes|scan_edges' && echo "(warnings in M3 code above)" || true
|
grep -i warning "$WORK/cc.log" | grep -iE 'engram_store|eg_store|eg_load|scan_nodes|scan_edges' && echo "(warnings in M3 code above)" || true
|
||||||
|
|
||||||
@@ -106,7 +112,7 @@ echo
|
|||||||
echo "== 5) ASan+UBSan build, exercise M3 scan/boot/hooks (leaks off — harness intentionally leaks el_strdup) =="
|
echo "== 5) ASan+UBSan build, exercise M3 scan/boot/hooks (leaks off — harness intentionally leaks el_strdup) =="
|
||||||
SANBIN="$WORK/m3.san"
|
SANBIN="$WORK/m3.san"
|
||||||
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
||||||
-I "$INC" "$HERE/test_m3_parity.c" "$RT" "$ST" -lcurl -o "$SANBIN" 2>"$WORK/san_cc.log"
|
-I "$INC" "$HERE/test_m3_parity.c" $RTSRC $SSLFLAGS -lcurl -lssl -lcrypto -lpthread -lm -o "$SANBIN" 2>"$WORK/san_cc.log"
|
||||||
if [ $? -ne 0 ]; then echo " SAN COMPILE FAILED:"; tail -20 "$WORK/san_cc.log"; fail=1; else
|
if [ $? -ne 0 ]; then echo " SAN COMPILE FAILED:"; tail -20 "$WORK/san_cc.log"; fail=1; else
|
||||||
export ASAN_OPTIONS=detect_leaks=0
|
export ASAN_OPTIONS=detect_leaks=0
|
||||||
DATA2="$WORK/data2"; mkdir -p "$DATA2"
|
DATA2="$WORK/data2"; mkdir -p "$DATA2"
|
||||||
|
|||||||
@@ -6,8 +6,14 @@
|
|||||||
# Writes ONLY under a throwaway /tmp dir with a throwaway HOME.
|
# Writes ONLY under a throwaway /tmp dir with a throwaway HOME.
|
||||||
set -u
|
set -u
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
RT="$HERE/../../lang/runtime/el_runtime.c"
|
RTSRC="$("$HERE/../../scripts/el-runtime-sources.sh" "$HERE/../../lang/runtime")"
|
||||||
ST="$HERE/../../lang/runtime/engram_store.c"
|
# The runtime is MULTI-FILE (lang/runtime/SOURCES). This harness used to link
|
||||||
|
# el_runtime.c + engram_store.c only, which stopped linking once el_runtime.c
|
||||||
|
# began calling into the other engram siblings. Unquoted on purpose: a list.
|
||||||
|
SSLFLAGS=""
|
||||||
|
if command -v brew >/dev/null 2>&1 && O="$(brew --prefix openssl@3 2>/dev/null)"; then
|
||||||
|
SSLFLAGS="-I$O/include -L$O/lib"
|
||||||
|
fi
|
||||||
INC="$HERE/../../lang/runtime"
|
INC="$HERE/../../lang/runtime"
|
||||||
WORK="$(mktemp -d /tmp/engram-m7-XXXXXX)"
|
WORK="$(mktemp -d /tmp/engram-m7-XXXXXX)"
|
||||||
DATA="$WORK/data"; mkdir -p "$DATA"
|
DATA="$WORK/data"; mkdir -p "$DATA"
|
||||||
@@ -22,7 +28,7 @@ unset ENGRAM_STORE
|
|||||||
fail=0
|
fail=0
|
||||||
|
|
||||||
echo "== compiling harness (gcc: el_runtime.c + engram_store.c + test_m7_traversal.c) =="
|
echo "== compiling harness (gcc: el_runtime.c + engram_store.c + test_m7_traversal.c) =="
|
||||||
gcc -O2 -std=c11 -I "$INC" "$HERE/test_m7_traversal.c" "$RT" "$ST" -lcurl -lm -o "$BIN" 2>"$WORK/cc.log"
|
gcc -O2 -std=c11 -I "$INC" "$HERE/test_m7_traversal.c" $RTSRC $SSLFLAGS -lcurl -lssl -lcrypto -lpthread -lm -o "$BIN" 2>"$WORK/cc.log"
|
||||||
if [ $? -ne 0 ]; then echo "COMPILE FAILED:"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; fi
|
if [ $? -ne 0 ]; then echo "COMPILE FAILED:"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; fi
|
||||||
echo " ok: compiled"
|
echo " ok: compiled"
|
||||||
|
|
||||||
@@ -115,7 +121,7 @@ echo
|
|||||||
echo "== 3) ASan+UBSan clean across parity + a small perf loop (leaks off — harness intentionally leaks el_strdup) =="
|
echo "== 3) ASan+UBSan clean across parity + a small perf loop (leaks off — harness intentionally leaks el_strdup) =="
|
||||||
SANBIN="$WORK/m7.san"
|
SANBIN="$WORK/m7.san"
|
||||||
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
||||||
-I "$INC" "$HERE/test_m7_traversal.c" "$RT" "$ST" -lcurl -lm -o "$SANBIN" 2>"$WORK/san_cc.log"
|
-I "$INC" "$HERE/test_m7_traversal.c" $RTSRC $SSLFLAGS -lcurl -lssl -lcrypto -lpthread -lm -lm -o "$SANBIN" 2>"$WORK/san_cc.log"
|
||||||
if [ $? -ne 0 ]; then echo " SAN COMPILE FAILED:"; tail -20 "$WORK/san_cc.log"; fail=1; else
|
if [ $? -ne 0 ]; then echo " SAN COMPILE FAILED:"; tail -20 "$WORK/san_cc.log"; fail=1; else
|
||||||
export ASAN_OPTIONS=detect_leaks=0
|
export ASAN_OPTIONS=detect_leaks=0
|
||||||
D2="$WORK/data2"; mkdir -p "$D2"
|
D2="$WORK/data2"; mkdir -p "$D2"
|
||||||
|
|||||||
@@ -3,8 +3,17 @@
|
|||||||
set -e
|
set -e
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
REL="$HERE/../../lang/runtime"
|
REL="$HERE/../../lang/runtime"
|
||||||
|
# test_wal.c and test_failloud.c #include "el_runtime.c" directly, so el_runtime.c
|
||||||
|
# is already IN the translation unit — link the SIBLINGS only, or every symbol in
|
||||||
|
# it is defined twice. The siblings are still required: el_runtime.c calls into
|
||||||
|
# all six engram TUs. (lang/runtime/SOURCES is the source of truth.)
|
||||||
|
RTSIB="$("$HERE/../../scripts/el-runtime-sources.sh" "$REL" | grep -v '/el_runtime\.c$')"
|
||||||
|
SSLFLAGS=""
|
||||||
|
if command -v brew >/dev/null 2>&1 && O="$(brew --prefix openssl@3 2>/dev/null)"; then
|
||||||
|
SSLFLAGS="-I$O/include -L$O/lib"
|
||||||
|
fi
|
||||||
cc -O2 -fbracket-depth=1024 -Wno-parentheses-equality -I"$REL" \
|
cc -O2 -fbracket-depth=1024 -Wno-parentheses-equality -I"$REL" \
|
||||||
"$HERE/test_wal.c" -lcurl -lpthread -o /tmp/test_wal
|
"$HERE/test_wal.c" $RTSIB $SSLFLAGS -lcurl -lssl -lcrypto -lpthread -lm -o /tmp/test_wal
|
||||||
HOME=/tmp/engram-throwaway-home /tmp/test_wal
|
HOME=/tmp/engram-throwaway-home /tmp/test_wal
|
||||||
# Fail-loud data-dir check (must exit 1 with a FATAL line):
|
# Fail-loud data-dir check (must exit 1 with a FATAL line):
|
||||||
cat > /tmp/test_failloud.c <<'C'
|
cat > /tmp/test_failloud.c <<'C'
|
||||||
@@ -12,5 +21,5 @@ cat > /tmp/test_failloud.c <<'C'
|
|||||||
int main(void){ unsetenv("ENGRAM_DATA_DIR"); unsetenv("HOME");
|
int main(void){ unsetenv("ENGRAM_DATA_DIR"); unsetenv("HOME");
|
||||||
engram_resolve_data_dir(); printf("REACHED\n"); return 0; }
|
engram_resolve_data_dir(); printf("REACHED\n"); return 0; }
|
||||||
C
|
C
|
||||||
cc -O2 -fbracket-depth=1024 -Wno-parentheses-equality -I"$REL" /tmp/test_failloud.c -lcurl -lpthread -o /tmp/test_failloud
|
cc -O2 -fbracket-depth=1024 -Wno-parentheses-equality -I"$REL" /tmp/test_failloud.c $RTSIB $SSLFLAGS -lcurl -lssl -lcrypto -lpthread -lm -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
|
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
|
||||||
|
|||||||
+34
-12
@@ -77,14 +77,30 @@ This is where almost all work belongs. El programs are source files that get com
|
|||||||
|
|
||||||
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 runtime is native El (`runtime/*.el`) over a C OS-boundary. **Status (verified 2026-08-15):** the migration to a seed-only boundary is *in progress, not done*. Two files exist:
|
The runtime is native El (`runtime/*.el`) over a C OS-boundary. **Status (verified 2026-08-16):** the migration to a seed-only boundary is *in progress, not done*.
|
||||||
- `runtime/el_runtime.c` (~860 KB) — **LIVE**. Holds the engram store (`EngramStore engram_global`) plus the `http_*`/`json_*`/`state_*`/`engram_*` impls. It is the authoritative single-file link target for the compiler, and `tools/install.sh` compiles it into `libel.a`. This is where a new C builtin's *implementation* must currently live to be linkable.
|
|
||||||
- `runtime/el_seed.c` — the intended hand-maintained `__`-prefixed seed (thin wrappers over the above). It is compiled alongside `el_runtime.c` by `tools/install.sh`, but does **not** compile standalone yet (see the build-path caveat under "Rebuilding the Compiler").
|
**The runtime is MULTI-FILE. There is no single-file link target and there has not been one for months.** The canonical link set is listed once, in **`runtime/SOURCES`**, and printed by `scripts/el-runtime-sources.sh`. It currently holds ten translation units: `el_runtime.c`, `el_seed.c`, the six `engram_*.c` concern files, and `eg_cosine_batch{,_strategy_cpu}.c`.
|
||||||
|
|
||||||
|
- `runtime/el_runtime.c` (~940 KB, 20.5k lines) — **LIVE, and oversized.** It began life on 2026-05-03 as a temporary build shim: it was deleted that afternoon ("runtime is 100% native El") and restored 25 minutes later, explicitly "UNTIL the compiler is updated to emit `#include el_seed.h`". That `until` never arrived, and in the 3.5 months since, the file doubled. **It is not a volatility unit — it is a dumping ground.** ~47.5% of it is engram code that belongs in the six sibling files that already exist. Do not add to it. See "Where a new C builtin goes" below.
|
||||||
|
- `runtime/el_seed.c` — the intended hand-maintained `__`-prefixed seed (thin wrappers over the above).
|
||||||
|
- `runtime/engram_{store,vindex,geometry,reason,verify,cognition}.c` — the engram concerns, each with its own header. `el_runtime.c` `#include`s all six headers and makes hard cross-TU calls into all six.
|
||||||
|
|
||||||
|
> **Linking `el_runtime.c` alone does not work and has not for months.** It fails at `ld` with undefined symbols (`engram_ground_json`, `engram_activate_inner`, `eg_find_relation`, `cog_assert_two_axis`, …). Any recipe, script, or CI step that names `el_runtime.c` by itself is stale — replace it with `$(scripts/el-runtime-sources.sh lang/runtime)`.
|
||||||
|
|
||||||
**Only edit these when you genuinely need OS-level access** (raw sockets, GPU calls, new libcurl features, a new engram store op). For everything else, write El.
|
**Only edit these when you genuinely need OS-level access** (raw sockets, GPU calls, new libcurl features, a new engram store op). For everything else, write El.
|
||||||
|
|
||||||
|
#### Where a new C builtin goes
|
||||||
|
|
||||||
|
**Put it in the `.c` that owns the concern — NOT in `el_runtime.c`.**
|
||||||
|
|
||||||
|
*Placement is a link-time concern. The compiler cannot tell which `.c` a symbol came from, and never could.* `builtin_arity` in `el-compiler/src/codegen.el` maps NAME → ARITY INT and nothing else (~413 entries); the El name is emitted as the exact C symbol and resolved by `ld`. Proof, if you want it: `nm lang/dist/platform/elc` on the *shipped* compiler shows `T _engram_geo_reify_index_new` (defined in `engram_geometry.c`), `T _vindex_insert` (`engram_vindex.c`), `T _engram_think` (`engram_cognition.c`), `T _engram_reason_abduce` (`engram_reason.c`). **The shipped compiler is already linked from ten translation units.** A builtin defined in a sibling `.c` is exactly as linkable as one defined in `el_runtime.c`.
|
||||||
|
|
||||||
|
Choose the file by concern: engram store ops → `engram_store.c`; index → `engram_vindex.c`; geometry/priming → `engram_geometry.c`; reasoning → `engram_reason.c`; grounding/consistency → `engram_verify.c`; think/stance → `engram_cognition.c`. **If no existing file owns it, create one** — add the `.c` to `runtime/SOURCES` (one line) and every build path picks it up. For a builtin that belongs to a downstream program rather than the runtime, declare `c_source "path/to/file.c"` in that program's `manifest.el`; `elb` already links it (`parse_manifest_c_sources`, `lang/elb.el:82`).
|
||||||
|
|
||||||
|
> **`el_runtime.c` is on a ratchet and will reject your commit.** `runtime/BUDGET` caps it at its current line count *with no headroom*, and separately caps the number of `engram_*`/`eg_*`/`cog_*` function definitions in it. `scripts/check-runtime-growth.sh` enforces both in CI and in `.githooks/pre-commit`. **The numbers may only ever go down — do not raise them.** Every other runtime file is deliberately uncapped, because that is where the code is supposed to go. When you move code *out*, lower the numbers in the same commit; the guard tells you the new values.
|
||||||
|
|
||||||
When you add a C builtin (verbatim-emit recipe — the El name is emitted as the exact C symbol; `builtin_arity` is an arity guard only, not a dispatch table):
|
When you add a C builtin (verbatim-emit recipe — the El name is emitted as the exact C symbol; `builtin_arity` is an arity guard only, not a dispatch table):
|
||||||
1. Implement the C function in `el_runtime.c` (and declare it in `el_runtime.h`).
|
1. Implement the C function in the **concern-owning `.c`** (and declare it in that file's `.h`). Add the file to `runtime/SOURCES` if it is new. Only put it in `el_runtime.c` if it is genuinely EL core (val/str/map/list/arena) — that is ~8% of what is in there today.
|
||||||
2. Add a `__`-prefixed thin wrapper in `el_seed.c` and declare it in `el_seed.h`.
|
2. Add a `__`-prefixed thin wrapper in `el_seed.c` and declare it in `el_seed.h`.
|
||||||
3. Add the name to `builtin_arity` in `el-compiler/src/codegen.el` — add **both** the plain and `__`-prefixed spellings.
|
3. Add the name to `builtin_arity` in `el-compiler/src/codegen.el` — add **both** the plain and `__`-prefixed spellings.
|
||||||
4. Rebuild the elc binary (see below) and confirm the self-host fixpoint is byte-identical.
|
4. Rebuild the elc binary (see below) and confirm the self-host fixpoint is byte-identical.
|
||||||
@@ -111,21 +127,27 @@ After changing any `.el` source in `el-compiler/src/` (run from the `lang/` dir)
|
|||||||
```bash
|
```bash
|
||||||
# 1. Stage2: current elc compiles the (modified) compiler to C
|
# 1. Stage2: current elc compiles the (modified) compiler to C
|
||||||
./dist/platform/elc elc-cli.el > elc-new.c
|
./dist/platform/elc elc-cli.el > elc-new.c
|
||||||
# 2. Build the new compiler. The C link target is el_runtime.c — it holds the
|
# 2. Build the new compiler. Link the WHOLE runtime set, not el_runtime.c alone:
|
||||||
# engram store + http/json/state impls the compiler output calls. el_runtime.c
|
# el_runtime.c calls into engram_store / engram_vindex / eg_cosine_batch and
|
||||||
# self-hosts elc on its own; el_seed.c is the (aspirational) seed layer and does
|
# wraps el_seed.c, so a one-file link fails at `ld` with undefined symbols
|
||||||
# NOT compile standalone under clang (missing prototypes for the el_runtime.c
|
# (verified 2026-08-16 — the previous single-file line in this doc is stale).
|
||||||
# symbols it wraps — see caveat below), so link el_runtime.c here.
|
cc -std=c11 -O2 -I runtime -I$(brew --prefix openssl@3)/include \
|
||||||
cc -std=c11 -I runtime -lcurl -lpthread \
|
-L$(brew --prefix openssl@3)/lib \
|
||||||
-o dist/platform/elc-new \
|
-o dist/platform/elc-new \
|
||||||
elc-new.c runtime/el_runtime.c
|
elc-new.c runtime/el_runtime.c runtime/el_seed.c \
|
||||||
|
runtime/engram_cognition.c runtime/engram_geometry.c runtime/engram_reason.c \
|
||||||
|
runtime/engram_store.c runtime/engram_verify.c runtime/engram_vindex.c \
|
||||||
|
runtime/eg_cosine_batch.c runtime/eg_cosine_batch_strategy_cpu.c \
|
||||||
|
-lcurl -lssl -lcrypto -lpthread -lm
|
||||||
# 3. Verify self-hosting FIXPOINT (stage3 == stage2 output, byte-identical):
|
# 3. Verify self-hosting FIXPOINT (stage3 == stage2 output, byte-identical):
|
||||||
./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 # must be identical
|
diff elc-new.c elc-verify.c # must be identical
|
||||||
mv dist/platform/elc-new dist/platform/elc
|
mv dist/platform/elc-new dist/platform/elc
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Build-path caveat (verified 2026-08-15).** `el_seed.c` is the intended hand-maintained OS-boundary seed, but it does **not** compile standalone under modern clang: it wraps ~16 unprefixed `el_runtime.c` symbols (`http_serve`, `json_*`, `state_*`, `http_response`) without prototypes, and clang treats implicit declarations as errors (C99+). The productionised install (`tools/install.sh`) builds `libel.a` from **both** `el_seed.o` + `el_runtime.o` together, which is why linking succeeds there. To make `el_seed.c` build on its own, add prototypes for those symbols (or `#include "el_runtime.h"`, reconciling the `__http_serve` return-type mismatch first). Until then, `el_runtime.c` is the authoritative single-file link target for the compiler.
|
> **Build-path caveat (verified 2026-08-15).** `el_seed.c` is the intended hand-maintained OS-boundary seed, but it does **not** compile standalone under modern clang: it wraps ~16 unprefixed `el_runtime.c` symbols (`http_serve`, `json_*`, `state_*`, `http_response`) without prototypes, and clang treats implicit declarations as errors (C99+). The productionised install (`tools/install.sh`) builds `libel.a` from **both** `el_seed.o` + `el_runtime.o` together, which is why linking succeeds there. To make `el_seed.c` build on its own, add prototypes for those symbols (or `#include "el_runtime.h"`, reconciling the `__http_serve` return-type mismatch first).
|
||||||
|
>
|
||||||
|
> **There is no single-file link target.** *(Corrected 2026-08-16 — this paragraph previously ended "`el_runtime.c` is the authoritative single-file link target for the compiler". Measured: that is false. Linking `elc-new.c` against `runtime/el_runtime.c` alone fails at `ld` with undefined `engram_ground_json`, `engram_activate_inner`, `eg_find_relation`, `cog_assert_two_axis`, and others, because `el_runtime.c` `#include`s six engram headers and calls into all six sibling `.c` files.)* Link the set in `runtime/SOURCES` via `$(../scripts/el-runtime-sources.sh runtime)`.
|
||||||
|
|
||||||
After changing `el_seed.c` only (no El source changes), rebuild downstream programs but do NOT need to rebuild the compiler binary itself — the seed is linked at the application level, not the compiler level.
|
After changing `el_seed.c` only (no El source changes), rebuild downstream programs but do NOT need to rebuild the compiler binary itself — the seed is linked at the application level, not the compiler level.
|
||||||
|
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
@@ -3295,6 +3295,12 @@ fn cgi_arg(value: String, has_value: Bool) -> String {
|
|||||||
// exit before touching configuration, ports, or any data directory.
|
// exit before touching configuration, ports, or any data directory.
|
||||||
// 2. config declarations — resolve env-or-default, one declaration per entry.
|
// 2. config declarations — resolve env-or-default, one declaration per entry.
|
||||||
// 3. validate LAST — report EVERY missing/ill-typed entry at once, then exit.
|
// 3. validate LAST — report EVERY missing/ill-typed entry at once, then exit.
|
||||||
|
//
|
||||||
|
// `singleton:` carries its `guards:` expression as its SECOND argument — the
|
||||||
|
// state the lock protects, evaluated here at the process boundary. A singleton
|
||||||
|
// without one does not compile (see below): a lock keyed on the program's name
|
||||||
|
// rather than on its state refuses unrelated instances and permits concurrent
|
||||||
|
// ones, which is not a weaker guard but a wrong one.
|
||||||
fn el_bool_arg(b: Bool) -> String {
|
fn el_bool_arg(b: Bool) -> String {
|
||||||
if b { return "EL_INT(1)" }
|
if b { return "EL_INT(1)" }
|
||||||
return "EL_INT(0)"
|
return "EL_INT(0)"
|
||||||
@@ -3306,7 +3312,16 @@ fn emit_program_init(stmt: Map<String, Any>) -> Void {
|
|||||||
let has_singleton: Bool = stmt["has_singleton"]
|
let has_singleton: Bool = stmt["has_singleton"]
|
||||||
if has_singleton {
|
if has_singleton {
|
||||||
let sid: String = stmt["singleton"]
|
let sid: String = stmt["singleton"]
|
||||||
emit_line(" el_singleton_acquire(EL_STR(" + c_str_lit(sid) + "));")
|
let has_guards: Bool = stmt["has_guards"]
|
||||||
|
if has_guards {
|
||||||
|
let guards_c: String = cg_expr(stmt["guards"])
|
||||||
|
emit_line(" el_singleton_acquire(EL_STR(" + c_str_lit(sid) + "), " + guards_c + ");")
|
||||||
|
} else {
|
||||||
|
// Refuse at COMPILE time. The alternative — emitting a name-keyed
|
||||||
|
// lock — is the defect itself, and it fails silently in the direction
|
||||||
|
// that loses data.
|
||||||
|
emit_line("#error \"singleton '" + sid + "' declares no `guards:` — a singleton must name the state it protects, e.g. `guards: engram_resolve_data_dir()` (spec 18.2)\"")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let entries = stmt["entries"]
|
let entries = stmt["entries"]
|
||||||
let n: Int = native_list_len(entries)
|
let n: Int = native_list_len(entries)
|
||||||
|
|||||||
@@ -1976,6 +1976,18 @@ fn parse_stmt(tokens: [Any], pos: Int) -> Map<String, Any> {
|
|||||||
// singleton: "id" — process identity. The runtime takes an exclusive
|
// singleton: "id" — process identity. The runtime takes an exclusive
|
||||||
// lock at startup; a SECOND start is refused, loudly,
|
// lock at startup; a SECOND start is refused, loudly,
|
||||||
// instead of two processes sharing one data dir.
|
// instead of two processes sharing one data dir.
|
||||||
|
// guards: <expr> — WHAT that singleton protects: an expression yielding
|
||||||
|
// the path of the guarded state directory, evaluated at
|
||||||
|
// startup. MANDATORY with `singleton:`, because a lock
|
||||||
|
// keyed on a program's NAME rather than on its STATE is
|
||||||
|
// not a guard — measured 2026-08-16, the name-keyed
|
||||||
|
// version refused unrelated instances (different data
|
||||||
|
// dirs) AND permitted concurrent ones (same data dir,
|
||||||
|
// different $TMPDIR). It is an expression and not a
|
||||||
|
// string so a program can point at the resolver that
|
||||||
|
// already OWNS the path (§18.4) instead of restating
|
||||||
|
// its default here, which would give the path two
|
||||||
|
// owners that can disagree.
|
||||||
// env NAME: T = "d" — one configuration entry. Its type and its default
|
// env NAME: T = "d" — one configuration entry. Its type and its default
|
||||||
// are declared ONCE, here, and resolved+validated
|
// are declared ONCE, here, and resolved+validated
|
||||||
// before main() body runs.
|
// before main() body runs.
|
||||||
@@ -1993,6 +2005,8 @@ fn parse_stmt(tokens: [Any], pos: Int) -> Map<String, Any> {
|
|||||||
let p = expect(tokens, p, "LBrace")
|
let p = expect(tokens, p, "LBrace")
|
||||||
let singleton = ""
|
let singleton = ""
|
||||||
let has_singleton = false
|
let has_singleton = false
|
||||||
|
let guards_node = { "expr": "Str", "value": "" }
|
||||||
|
let has_guards = false
|
||||||
let entries = native_list_empty()
|
let entries = native_list_empty()
|
||||||
// Entry-scratch declared at loop-body level (not inside the branch) so
|
// Entry-scratch declared at loop-body level (not inside the branch) so
|
||||||
// that inner `let` forms compile to assignment rather than a C-scoped
|
// that inner `let` forms compile to assignment rather than a C-scoped
|
||||||
@@ -2048,13 +2062,26 @@ fn parse_stmt(tokens: [Any], pos: Int) -> Map<String, Any> {
|
|||||||
"required": erequired
|
"required": erequired
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// scalar field: `name: "value"`
|
if str_eq(fname, "guards") {
|
||||||
let p = expect(tokens, p, "Colon")
|
// guards: <expr> — the STATE the singleton protects.
|
||||||
let fval = tok_value(tokens, p)
|
// Parsed as a full expression, not a string literal, so
|
||||||
let p = p + 1
|
// it can name the resolver that owns the path
|
||||||
if str_eq(fname, "singleton") {
|
// (`guards: engram_resolve_data_dir()`) rather than
|
||||||
let singleton = fval
|
// duplicating that resolver's default here.
|
||||||
let has_singleton = true
|
let p = expect(tokens, p, "Colon")
|
||||||
|
let g_r = parse_expr(tokens, p)
|
||||||
|
let guards_node = g_r["node"]
|
||||||
|
let p = g_r["pos"]
|
||||||
|
let has_guards = true
|
||||||
|
} else {
|
||||||
|
// scalar field: `name: "value"`
|
||||||
|
let p = expect(tokens, p, "Colon")
|
||||||
|
let fval = tok_value(tokens, p)
|
||||||
|
let p = p + 1
|
||||||
|
if str_eq(fname, "singleton") {
|
||||||
|
let singleton = fval
|
||||||
|
let has_singleton = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let k5 = tok_kind(tokens, p)
|
let k5 = tok_kind(tokens, p)
|
||||||
@@ -2070,6 +2097,8 @@ fn parse_stmt(tokens: [Any], pos: Int) -> Map<String, Any> {
|
|||||||
"name": name,
|
"name": name,
|
||||||
"singleton": singleton,
|
"singleton": singleton,
|
||||||
"has_singleton": has_singleton,
|
"has_singleton": has_singleton,
|
||||||
|
"guards": guards_node,
|
||||||
|
"has_guards": has_guards,
|
||||||
"entries": entries
|
"entries": entries
|
||||||
}, p)
|
}, p)
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-9
@@ -49,21 +49,49 @@ download() {
|
|||||||
TMP_DIR="$(mktemp -d)"
|
TMP_DIR="$(mktemp -d)"
|
||||||
trap 'rm -rf "${TMP_DIR}"' EXIT
|
trap 'rm -rf "${TMP_DIR}"' EXIT
|
||||||
|
|
||||||
download "${RELEASE_BASE}/elc" "${TMP_DIR}/elc"
|
# The runtime is MULTI-FILE. el_runtime.c #includes six engram headers and makes
|
||||||
download "${RELEASE_BASE}/el_runtime.c" "${TMP_DIR}/el_runtime.c"
|
# hard cross-TU calls into all six sibling .c files, so installing el_runtime.c
|
||||||
download "${RELEASE_BASE}/el_runtime.h" "${TMP_DIR}/el_runtime.h"
|
# alone produces a lib/ that CANNOT LINK — `ld` fails with undefined
|
||||||
|
# engram_ground_json / engram_activate_inner / eg_find_relation / cog_assert_two_axis.
|
||||||
|
# This list mirrors lang/runtime/SOURCES (the in-repo source of truth); keep them
|
||||||
|
# in step. install.sh is standalone by design — it runs on machines with no repo
|
||||||
|
# checkout — so it cannot call scripts/el-runtime-sources.sh.
|
||||||
|
RUNTIME_SOURCES=(
|
||||||
|
el_runtime.c el_seed.c
|
||||||
|
engram_store.c engram_vindex.c engram_geometry.c
|
||||||
|
engram_reason.c engram_verify.c engram_cognition.c
|
||||||
|
engram_text.c
|
||||||
|
eg_cosine_batch.c eg_cosine_batch_strategy_cpu.c
|
||||||
|
)
|
||||||
|
RUNTIME_HEADERS=(
|
||||||
|
el_runtime.h el_seed.h
|
||||||
|
engram_store.h engram_vindex.h engram_geometry.h
|
||||||
|
engram_reason.h engram_verify.h engram_cognition.h
|
||||||
|
engram_text.h
|
||||||
|
eg_cosine_batch.h eg_cosine_batch_strategy.h
|
||||||
|
)
|
||||||
|
|
||||||
|
download "${RELEASE_BASE}/elc" "${TMP_DIR}/elc"
|
||||||
|
for f in "${RUNTIME_SOURCES[@]}" "${RUNTIME_HEADERS[@]}"; do
|
||||||
|
download "${RELEASE_BASE}/${f}" "${TMP_DIR}/${f}"
|
||||||
|
done
|
||||||
|
|
||||||
# Install
|
# Install
|
||||||
install -m 755 "${TMP_DIR}/elc" "${BIN_DIR}/elc"
|
install -m 755 "${TMP_DIR}/elc" "${BIN_DIR}/elc"
|
||||||
install -m 644 "${TMP_DIR}/el_runtime.c" "${LIB_DIR}/el_runtime.c"
|
for f in "${RUNTIME_SOURCES[@]}" "${RUNTIME_HEADERS[@]}"; do
|
||||||
install -m 644 "${TMP_DIR}/el_runtime.h" "${LIB_DIR}/el_runtime.h"
|
install -m 644 "${TMP_DIR}/${f}" "${LIB_DIR}/${f}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Record the link set so downstream Makefiles can read it instead of hardcoding.
|
||||||
|
printf '%s\n' "${RUNTIME_SOURCES[@]}" > "${TMP_DIR}/SOURCES"
|
||||||
|
install -m 644 "${TMP_DIR}/SOURCES" "${LIB_DIR}/SOURCES"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "==> El SDK installed successfully"
|
echo "==> El SDK installed successfully"
|
||||||
echo
|
echo
|
||||||
echo " elc binary : ${BIN_DIR}/elc"
|
echo " elc binary : ${BIN_DIR}/elc"
|
||||||
echo " runtime : ${LIB_DIR}/el_runtime.c"
|
echo " runtime : ${LIB_DIR}/ (${#RUNTIME_SOURCES[@]} .c files, ${#RUNTIME_HEADERS[@]} headers)"
|
||||||
echo " header : ${LIB_DIR}/el_runtime.h"
|
echo " link set : ${LIB_DIR}/SOURCES"
|
||||||
echo
|
echo
|
||||||
echo "Add the following to your Makefile to build El programs:"
|
echo "Add the following to your Makefile to build El programs:"
|
||||||
echo
|
echo
|
||||||
@@ -71,10 +99,14 @@ echo " EL_LIB := ${LIB_DIR}"
|
|||||||
echo " ELC := elc"
|
echo " ELC := elc"
|
||||||
echo " CC := cc"
|
echo " CC := cc"
|
||||||
echo " CFLAGS := -std=c11 -O2 -I\$(EL_LIB)"
|
echo " CFLAGS := -std=c11 -O2 -I\$(EL_LIB)"
|
||||||
|
echo " LDLIBS := -lcurl -lssl -lcrypto -lpthread -lm"
|
||||||
|
echo
|
||||||
|
echo " # The runtime is multi-file — link the whole set, not el_runtime.c alone."
|
||||||
|
echo " EL_RUNTIME := \$(addprefix \$(EL_LIB)/,\$(shell cat \$(EL_LIB)/SOURCES))"
|
||||||
echo
|
echo
|
||||||
echo " dist/myapp.c: src/myapp.el"
|
echo " dist/myapp.c: src/myapp.el"
|
||||||
echo " \t\$(ELC) src/myapp.el > dist/myapp.c"
|
echo " \t\$(ELC) src/myapp.el > dist/myapp.c"
|
||||||
echo
|
echo
|
||||||
echo " dist/myapp: dist/myapp.c"
|
echo " dist/myapp: dist/myapp.c"
|
||||||
echo " \t\$(CC) \$(CFLAGS) -o dist/myapp dist/myapp.c \$(EL_LIB)/el_runtime.c -lcurl -lpthread"
|
echo " \t\$(CC) \$(CFLAGS) -o dist/myapp dist/myapp.c \$(EL_RUNTIME) \$(LDLIBS)"
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# BUDGET — a RATCHET on lang/runtime/el_runtime.c. Enforced by
|
||||||
|
# scripts/check-runtime-growth.sh. These numbers may only ever go DOWN.
|
||||||
|
#
|
||||||
|
# WHY THIS FILE EXISTS
|
||||||
|
# --------------------
|
||||||
|
# scripts/check-single-runtime.sh guards against el_runtime.c being COPIED.
|
||||||
|
# Nothing guarded against it GROWING. It grew from 10,607 lines to 20,527 —
|
||||||
|
# 94% — in 3.5 months, while under an explicit commit-message promise that it
|
||||||
|
# was a temporary shim about to be deleted.
|
||||||
|
#
|
||||||
|
# It grew because lang/AGENTS.md told every agent to grow it: it claimed
|
||||||
|
# el_runtime.c was "the authoritative single-file link target" and that a new
|
||||||
|
# C builtin "must live there to be linkable". That is false — placement is a
|
||||||
|
# link-time concern, `builtin_arity` is an arity guard not a dispatch table,
|
||||||
|
# and the shipped elc already links from ten translation units. The claim is
|
||||||
|
# corrected, and this file is the mechanism that keeps it corrected.
|
||||||
|
#
|
||||||
|
# THIS IS A RATCHET, NOT A LIMIT
|
||||||
|
# ------------------------------
|
||||||
|
# The budget is set at the CURRENT size. There is no headroom, deliberately.
|
||||||
|
# The file cannot grow by even one line. Any new code goes in the .c that owns
|
||||||
|
# the concern — that is the whole point, and every other runtime file is
|
||||||
|
# deliberately UNCAPPED.
|
||||||
|
#
|
||||||
|
# When you move code OUT, lower the number in the same commit. The guard tells
|
||||||
|
# you to when you have earned it.
|
||||||
|
#
|
||||||
|
# FORMAT: <key> <value> — `#` comments and blank lines ignored.
|
||||||
|
|
||||||
|
# Maximum lines in lang/runtime/el_runtime.c.
|
||||||
|
# 2026-08-16: 20,527 — the high-water mark.
|
||||||
|
# 2026-08-16: 20,427 — engram_text.c extracted (tokenize, token hygiene,
|
||||||
|
# word-boundary match, damage signature). Ratcheted down.
|
||||||
|
max_lines 20427
|
||||||
|
|
||||||
|
# Maximum top-level engram/eg_/cog_ function definitions in el_runtime.c.
|
||||||
|
# ~47.5% of the file is engram code, and engram already owns six dedicated
|
||||||
|
# sibling files (engram_{store,vindex,geometry,reason,verify,cognition}.c).
|
||||||
|
# Every one of these belongs in one of them. This is the Stage 3 scoreboard.
|
||||||
|
# 2026-08-16: 279 -> 275 (4 moved to engram_text.c).
|
||||||
|
max_engram_fns 275
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# SOURCES — the canonical El runtime link set.
|
||||||
|
#
|
||||||
|
# THIS FILE IS THE SINGLE SOURCE OF TRUTH for "what do I compile and link to
|
||||||
|
# get the El runtime". Every build path — CI, install.sh, the SDK release, the
|
||||||
|
# docs, elb, the engram test harnesses — reads it via scripts/el-runtime-sources.sh
|
||||||
|
# instead of hardcoding its own list.
|
||||||
|
#
|
||||||
|
# WHY THIS FILE EXISTS
|
||||||
|
# --------------------
|
||||||
|
# The runtime has been multi-translation-unit since the engram siblings landed:
|
||||||
|
# el_runtime.c #includes engram_{store,vindex,geometry,reason,verify,cognition}.h
|
||||||
|
# and makes hard cross-TU calls into all six. Linking el_runtime.c ALONE has been
|
||||||
|
# broken since then — `ld` fails with undefined symbols (engram_ground_json,
|
||||||
|
# engram_activate_inner, eg_find_relation, cog_assert_two_axis, ...).
|
||||||
|
#
|
||||||
|
# It stayed broken because the link set was written out longhand in ~8 different
|
||||||
|
# places, each of which drifted independently. A list copied 8 times is a list
|
||||||
|
# that is wrong in 8 places. It is now written once, here.
|
||||||
|
#
|
||||||
|
# HOW TO USE IT
|
||||||
|
# -------------
|
||||||
|
# scripts/el-runtime-sources.sh # bare names, one per line
|
||||||
|
# scripts/el-runtime-sources.sh lang/runtime # prefixed with a directory
|
||||||
|
# cc ... $(scripts/el-runtime-sources.sh lang/runtime) -lcurl -lssl -lcrypto -lpthread -lm
|
||||||
|
#
|
||||||
|
# ADDING A FILE
|
||||||
|
# -------------
|
||||||
|
# Add the .c here and it is picked up by every build path at once. That is the
|
||||||
|
# point: a new concern gets its own translation unit and costs one line, instead
|
||||||
|
# of being appended to el_runtime.c because appending was the cheaper edit.
|
||||||
|
#
|
||||||
|
# Order is link order. Blank lines and `#` comments are ignored.
|
||||||
|
|
||||||
|
# --- EL core language runtime -------------------------------------------------
|
||||||
|
el_runtime.c
|
||||||
|
el_seed.c
|
||||||
|
|
||||||
|
# --- Engram: store, index, geometry, reasoning, verification, cognition -------
|
||||||
|
# These are the six concern-owned translation units el_runtime.c calls into.
|
||||||
|
engram_store.c
|
||||||
|
engram_vindex.c
|
||||||
|
engram_geometry.c
|
||||||
|
engram_reason.c
|
||||||
|
engram_verify.c
|
||||||
|
engram_cognition.c
|
||||||
|
|
||||||
|
# --- Text: tokenization, token hygiene, damage signature ---------------------
|
||||||
|
# Extracted from el_runtime.c 2026-08-16. Plain C over <ctype.h>/<string.h> —
|
||||||
|
# touches no EL value type and no engram store type. New text helpers go HERE.
|
||||||
|
engram_text.c
|
||||||
|
|
||||||
|
# --- Vector math: batch cosine + its CPU strategy ----------------------------
|
||||||
|
# The ggml strategy (eg_cosine_batch_strategy_ggml.c) is an OPTIONAL swap-in and
|
||||||
|
# is deliberately NOT in the default set — it needs ggml headers. Link it in
|
||||||
|
# place of the cpu strategy when you have them.
|
||||||
|
eg_cosine_batch.c
|
||||||
|
eg_cosine_batch_strategy_cpu.c
|
||||||
+101
-124
@@ -8638,6 +8638,7 @@ static char* engram_first_n_chars(const char* s, size_t n) {
|
|||||||
* mutation (node/edge create, forget) is mirrored through the store's
|
* mutation (node/edge create, forget) is mirrored through the store's
|
||||||
* WAL-logged API so neuron.egm/neuron.wal stay authoritative.
|
* WAL-logged API so neuron.egm/neuron.wal stay authoritative.
|
||||||
* ══════════════════════════════════════════════════════════════════════════ */
|
* ══════════════════════════════════════════════════════════════════════════ */
|
||||||
|
#include "engram_text.h" /* text: tokenize, token hygiene, loss signature */
|
||||||
#include "engram_store.h"
|
#include "engram_store.h"
|
||||||
#include "engram_vindex.h" /* M8: ANN (HNSW) index for activation seed selection */
|
#include "engram_vindex.h" /* M8: ANN (HNSW) index for activation seed selection */
|
||||||
#include "engram_geometry.h" /* M9: centered relational-neighborhood geometry (priming) */
|
#include "engram_geometry.h" /* M9: centered relational-neighborhood geometry (priming) */
|
||||||
@@ -9061,31 +9062,9 @@ el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) {
|
|||||||
* RIGHT NOW" — which is the regression question, and the one that
|
* RIGHT NOW" — which is the regression question, and the one that
|
||||||
* would have caught this in a day instead of two months.
|
* would have caught this in a day instead of two months.
|
||||||
*
|
*
|
||||||
* SIGNATURE. Conservative on purpose — a false alarm that cries corruption
|
* The SIGNATURE itself (eg_text_loss_signature) moved to engram_text.c on
|
||||||
* over ordinary punctuation is worse than useless. Two patterns, both of
|
* 2026-08-16 — it is plain C over <ctype.h> and touches nothing in here. The
|
||||||
* which are essentially absent from well-formed English prose:
|
* stock/flow gauges below stay, because they touch store and EL value types. */
|
||||||
* (a) alnum '?' alnum — "na?ve", "caf?s", "don?t". A real question mark
|
|
||||||
* never sits between two word characters.
|
|
||||||
* (b) ' ? ' followed by a lowercase letter — a lost em/en dash. A real
|
|
||||||
* question mark is not preceded by a space, and
|
|
||||||
* what follows one starts a new sentence.
|
|
||||||
* Deliberately NOT flagged: a trailing '?' after a word, '? ' before a
|
|
||||||
* capital, or '?' at end of string — all legitimate. This under-counts (it
|
|
||||||
* cannot see a mangled 'café ' where the '?' landed before a space), so the
|
|
||||||
* census is a floor on the damage, never an exaggeration of it. */
|
|
||||||
static int eg_text_loss_signature(const char* s) {
|
|
||||||
if (!s) return 0;
|
|
||||||
for (const char* p = s; *p; p++) {
|
|
||||||
if (*p != '?') continue;
|
|
||||||
unsigned char prev = (p == s) ? 0 : (unsigned char)p[-1];
|
|
||||||
unsigned char next = (unsigned char)p[1];
|
|
||||||
/* (a) sandwiched between word characters. */
|
|
||||||
if (isalnum(prev) && isalnum(next)) return 1;
|
|
||||||
/* (b) spaced, with lowercase continuation — a lost dash. */
|
|
||||||
if (prev == ' ' && next == ' ' && islower((unsigned char)p[2])) return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Damaged-node creations since process start. See the block comment above. */
|
/* Damaged-node creations since process start. See the block comment above. */
|
||||||
static int64_t _eg_txt_write_damaged = 0;
|
static int64_t _eg_txt_write_damaged = 0;
|
||||||
@@ -9697,37 +9676,7 @@ static int istr_contains(const char* hay, const char* needle) {
|
|||||||
* fix landed but never reached this release runtime — the copy the engram
|
* fix landed but never reached this release runtime — the copy the engram
|
||||||
* binary actually builds against.) */
|
* binary actually builds against.) */
|
||||||
#define ENGRAM_MAX_QTOKENS 32
|
#define ENGRAM_MAX_QTOKENS 32
|
||||||
#define ENGRAM_QTOK_LEN 256
|
/* ENGRAM_QTOK_LEN and engram_tokenize_query moved to engram_text.h/.c. */
|
||||||
|
|
||||||
/* Split q on whitespace into up to ENGRAM_MAX_QTOKENS distinct
|
|
||||||
* (case-insensitive) tokens. Returns the token count. Over-long tokens are
|
|
||||||
* truncated to ENGRAM_QTOK_LEN-1; over-count tokens are ignored. */
|
|
||||||
static int engram_tokenize_query(const char* q,
|
|
||||||
char toks[][ENGRAM_QTOK_LEN], int maxtok) {
|
|
||||||
int n = 0;
|
|
||||||
if (!q) return 0;
|
|
||||||
const char* p = q;
|
|
||||||
while (*p && n < maxtok) {
|
|
||||||
while (*p && isspace((unsigned char)*p)) p++;
|
|
||||||
if (!*p) break;
|
|
||||||
char buf[ENGRAM_QTOK_LEN];
|
|
||||||
size_t tl = 0;
|
|
||||||
while (*p && !isspace((unsigned char)*p)) {
|
|
||||||
if (tl < sizeof(buf) - 1) buf[tl++] = *p;
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
buf[tl] = '\0';
|
|
||||||
if (tl == 0) continue;
|
|
||||||
int dup = 0;
|
|
||||||
for (int s = 0; s < n; s++) {
|
|
||||||
if (strcasecmp(toks[s], buf) == 0) { dup = 1; break; }
|
|
||||||
}
|
|
||||||
if (dup) continue;
|
|
||||||
memcpy(toks[n], buf, tl + 1);
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Count how many of the ntok distinct query tokens appear (case-insensitive)
|
/* Count how many of the ntok distinct query tokens appear (case-insensitive)
|
||||||
* in the node's content, label, or tags. 0 == no match. */
|
* in the node's content, label, or tags. 0 == no match. */
|
||||||
@@ -16389,29 +16338,6 @@ el_val_t engram_label_df(el_val_t term) {
|
|||||||
#define ENGRAM_ST_TOKLEN 64
|
#define ENGRAM_ST_TOKLEN 64
|
||||||
#define ENGRAM_ST_SCANCHARS 400
|
#define ENGRAM_ST_SCANCHARS 400
|
||||||
|
|
||||||
/* Trim leading/trailing non-alphanumerics, then accept only tokens whose core
|
|
||||||
* is alphanumeric plus '-' and '_' with at least 3 letters. This subsumes the
|
|
||||||
* quoted-title guard (2026-07-25) and the "<!--" flood (2026-08-03)
|
|
||||||
* structurally: markup and punctuation-bearing tokens never become
|
|
||||||
* candidates, rather than being blocklisted after the fact. */
|
|
||||||
static int eg_st_clean_token(const char* raw, size_t rawlen,
|
|
||||||
char* out, size_t outcap) {
|
|
||||||
size_t s = 0, e = rawlen;
|
|
||||||
while (s < e && !isalnum((unsigned char)raw[s])) s++;
|
|
||||||
while (e > s && !isalnum((unsigned char)raw[e - 1])) e--;
|
|
||||||
size_t len = e - s;
|
|
||||||
if (len < 4 || len >= outcap) return 0;
|
|
||||||
int alpha = 0;
|
|
||||||
for (size_t i = 0; i < len; i++) {
|
|
||||||
unsigned char c = (unsigned char)raw[s + i];
|
|
||||||
if (isalpha(c)) alpha++;
|
|
||||||
else if (!isdigit(c) && c != '-' && c != '_') return 0;
|
|
||||||
}
|
|
||||||
if (alpha < 3) return 0;
|
|
||||||
memcpy(out, raw + s, len);
|
|
||||||
out[len] = '\0';
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ENGRAM_ST_DEBUG=1 dumps the full scored candidate set to stderr. One
|
/* ENGRAM_ST_DEBUG=1 dumps the full scored candidate set to stderr. One
|
||||||
* cached branch in production. This exists because the first live run of this
|
* cached branch in production. This exists because the first live run of this
|
||||||
@@ -16424,32 +16350,6 @@ static int _eg_st_debug(void) {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Word-boundary document frequency. engram_label_df uses istr_contains, i.e.
|
|
||||||
* SUBSTRING matching, and that is the wrong estimator for term specificity on
|
|
||||||
* short tokens: "them" hits inside "theme" and "anthem", "about" and "whole"
|
|
||||||
* come back with df 2 and 1 rather than 0. That matters here specifically
|
|
||||||
* because the min_df floor is what rejects English function words, and it can
|
|
||||||
* only do that job if their df is honestly zero. Substring df quietly handed
|
|
||||||
* them a survival ticket. Measured on the live store before this fix, "whole"
|
|
||||||
* (df=1, idf=8.76) and "about" (df=2, idf=8.36) were outscoring real topical
|
|
||||||
* terms and losing only on position — one node whose text happened to open
|
|
||||||
* with a function word would have seeded on it.
|
|
||||||
*
|
|
||||||
* engram_label_df keeps substring semantics: it is a separate published
|
|
||||||
* measure with existing callers, and changing it underneath them is not this
|
|
||||||
* change's business. */
|
|
||||||
static int eg_st_label_has_word(const char* hay, const char* word) {
|
|
||||||
size_t wl = strlen(word);
|
|
||||||
for (const char* p = hay; *p; p++) {
|
|
||||||
if (strncasecmp(p, word, wl) != 0) continue;
|
|
||||||
char before = (p == hay) ? '\0' : p[-1];
|
|
||||||
char after = p[wl];
|
|
||||||
if (before && (isalnum((unsigned char)before) || before == '_')) continue;
|
|
||||||
if (after && (isalnum((unsigned char)after) || after == '_')) continue;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* YAKE T_Case, adapted to this corpus. YAKE up-weights all-caps tokens
|
/* YAKE T_Case, adapted to this corpus. YAKE up-weights all-caps tokens
|
||||||
* because in ordinary prose an acronym is rare and carries topic. That
|
* because in ordinary prose an acronym is rare and carries topic. That
|
||||||
@@ -19809,22 +19709,84 @@ void log_warn(el_val_t msg_v) {
|
|||||||
* become a convention. */
|
* become a convention. */
|
||||||
static int el_singleton_fd = -1;
|
static int el_singleton_fd = -1;
|
||||||
static char el_singleton_path[1024];
|
static char el_singleton_path[1024];
|
||||||
|
static char el_singleton_state[1024];
|
||||||
|
|
||||||
static const char* el_singleton_dir(void) {
|
/* el_singleton_acquire — claim exclusive use of the guarded STATE, or refuse to
|
||||||
const char* d = getenv("EL_SINGLETON_DIR");
|
* start. Compiler-injected as the FIRST statement of main() for any program
|
||||||
if (d && *d) return d;
|
* whose `program` block declares `singleton:` (which must also declare
|
||||||
d = getenv("TMPDIR");
|
* `guards:` — see lang/spec/language.md §18.2).
|
||||||
if (d && *d) return d;
|
*
|
||||||
return "/tmp";
|
* GUARD THE THING, NOT THE NAME.
|
||||||
}
|
*
|
||||||
|
* Until 2026-08-16 this lock was keyed on the program's NAME and on $TMPDIR —
|
||||||
/* el_singleton_acquire — claim exclusive process identity, or refuse to start.
|
* `$EL_SINGLETON_DIR|$TMPDIR|/tmp` + `/el-singleton-<name>.lock` — and never
|
||||||
* Compiler-injected as the FIRST statement of main() for any program whose
|
* consulted the state it claimed to protect. Its own refusal message said
|
||||||
* `program` block declares `singleton:`. */
|
* "Refusing to start a second instance against the same state" while it had not
|
||||||
el_val_t el_singleton_acquire(el_val_t id_v) {
|
* looked at any state. Measured, it failed in BOTH directions:
|
||||||
|
*
|
||||||
|
* - FALSE POSITIVE: two engrams against genuinely DIFFERENT data dirs could
|
||||||
|
* not coexist. The second was refused, naming the first's pid — for sharing
|
||||||
|
* a name, not a store.
|
||||||
|
* - FALSE NEGATIVE (the dangerous one): `TMPDIR=/tmp/other` let a second
|
||||||
|
* instance start against the SAME data dir with no complaint. That is
|
||||||
|
* exactly the two-instance data-loss condition the guard exists to prevent,
|
||||||
|
* and the workaround was one environment variable.
|
||||||
|
*
|
||||||
|
* Both are one error: the identity of the resource had been replaced by a label
|
||||||
|
* for it. The fix is to put the lock file INSIDE the state it guards:
|
||||||
|
*
|
||||||
|
* <state>/.el-singleton-<id>.lock
|
||||||
|
*
|
||||||
|
* That placement is the whole mechanism, and it is why there is no hashing, no
|
||||||
|
* canonical-path registry, and no environment variable left to subvert:
|
||||||
|
*
|
||||||
|
* - Same directory => same file => same inode => the flock CONTENDS. There is
|
||||||
|
* no TMPDIR in the key, so there is nothing to change to get past it.
|
||||||
|
* - Different dirs => different files => no contention. Two stores are two
|
||||||
|
* stores; they were never in conflict and are no longer treated as if they
|
||||||
|
* were.
|
||||||
|
* - Different SPELLINGS of one directory — trailing slash, `x/../x`, a symlink
|
||||||
|
* — resolve to the same inode in the kernel's own path walk, so they contend
|
||||||
|
* without this code comparing strings at all. Path canonicalisation here is
|
||||||
|
* for the human-readable message, never for the decision.
|
||||||
|
*
|
||||||
|
* Kept, deliberately, from the version this replaces: it is an flock and not a
|
||||||
|
* pidfile (the kernel releases it on crash and on SIGKILL, so there is no stale
|
||||||
|
* state and therefore no "delete the lock file to get unstuck" ritual), and it
|
||||||
|
* reports the HOLDER'S PID (added because a stale process survived `pkill -f`
|
||||||
|
* and went on answering probes; "already running" is not actionable, a pid is).
|
||||||
|
*
|
||||||
|
* Changed: the message is now TRUE. It says "the same state" because the lock it
|
||||||
|
* failed to take lives in that state, and it names the state it checked. */
|
||||||
|
el_val_t el_singleton_acquire(el_val_t id_v, el_val_t state_v) {
|
||||||
const char* id = EL_CSTR(id_v);
|
const char* id = EL_CSTR(id_v);
|
||||||
if (!id || !*id) return EL_NULL;
|
if (!id || !*id) return EL_NULL;
|
||||||
|
|
||||||
|
/* A singleton with nothing to guard is the defect this function exists to
|
||||||
|
* remove; refuse rather than silently fall back to name-keying. The compiler
|
||||||
|
* rejects `singleton:` without `guards:`, so reaching this is a toolchain
|
||||||
|
* mismatch, not a user mistake — say so. */
|
||||||
|
const char* state = EL_CSTR(state_v);
|
||||||
|
if (!state || !*state) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"[el] FATAL: singleton '%s' was given no state to guard.\n"
|
||||||
|
"[el] A lock keyed on a program's NAME instead of on the state it\n"
|
||||||
|
"[el] protects is not a guard: it refuses unrelated instances and\n"
|
||||||
|
"[el] permits concurrent ones. Declare `guards: <path>` alongside\n"
|
||||||
|
"[el] `singleton:` in the program block (spec §18.2).\n", id);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Canonicalise so the operator is told WHICH directory was checked, in one
|
||||||
|
* spelling, whatever spelling they typed. This is a readability measure, not
|
||||||
|
* the mechanism: realpath() may fail (the directory may not exist yet) and
|
||||||
|
* correctness must not depend on it — when it succeeds it names the same
|
||||||
|
* directory, and when it does not we fall back to the path as given and the
|
||||||
|
* kernel's own path walk still collapses the spellings at open() time. */
|
||||||
|
char* rp = realpath(state, NULL);
|
||||||
|
snprintf(el_singleton_state, sizeof(el_singleton_state), "%s", rp ? rp : state);
|
||||||
|
free(rp);
|
||||||
|
|
||||||
/* Sanitise the id into a filename. */
|
/* Sanitise the id into a filename. */
|
||||||
char safe[256];
|
char safe[256];
|
||||||
size_t si = 0;
|
size_t si = 0;
|
||||||
@@ -19835,13 +19797,25 @@ el_val_t el_singleton_acquire(el_val_t id_v) {
|
|||||||
safe[si++] = (char)(ok ? c : '-');
|
safe[si++] = (char)(ok ? c : '-');
|
||||||
}
|
}
|
||||||
safe[si] = '\0';
|
safe[si] = '\0';
|
||||||
|
/* THE MECHANISM: the lock lives inside the state it guards. Two spellings of
|
||||||
|
* one directory name one file; two directories name two files. Note there is
|
||||||
|
* no $TMPDIR and no $EL_SINGLETON_DIR in this path — the escape hatch that
|
||||||
|
* made the guard bypassable is gone because there is nowhere left to put it. */
|
||||||
snprintf(el_singleton_path, sizeof(el_singleton_path),
|
snprintf(el_singleton_path, sizeof(el_singleton_path),
|
||||||
"%s/el-singleton-%s.lock", el_singleton_dir(), safe);
|
"%s/.el-singleton-%s.lock", el_singleton_state, safe);
|
||||||
|
|
||||||
int fd = open(el_singleton_path, O_RDWR | O_CREAT, 0644);
|
int fd = open(el_singleton_path, O_RDWR | O_CREAT, 0644);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
fprintf(stderr, "[el] FATAL: singleton '%s': cannot open lock file %s: %s\n",
|
/* Unguardable state. Refusing is the only honest option: starting anyway
|
||||||
id, el_singleton_path, strerror(errno));
|
* would mean running unguarded against exactly the store the guard is
|
||||||
|
* here to protect. */
|
||||||
|
fprintf(stderr,
|
||||||
|
"[el] FATAL: singleton '%s': cannot open the lock inside the state it guards.\n"
|
||||||
|
"[el] state: %s\n"
|
||||||
|
"[el] lock: %s (%s)\n"
|
||||||
|
"[el] The guarded directory must exist and be writable. Refusing to\n"
|
||||||
|
"[el] start unguarded against it.\n",
|
||||||
|
id, el_singleton_state, el_singleton_path, strerror(errno));
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (flock(fd, LOCK_EX | LOCK_NB) != 0) {
|
if (flock(fd, LOCK_EX | LOCK_NB) != 0) {
|
||||||
@@ -19857,11 +19831,14 @@ el_val_t el_singleton_acquire(el_val_t id_v) {
|
|||||||
fprintf(stderr, "[el] FATAL: another instance of '%s' is already running", id);
|
fprintf(stderr, "[el] FATAL: another instance of '%s' is already running", id);
|
||||||
if (holder > 0) fprintf(stderr, " (pid %ld)", holder);
|
if (holder > 0) fprintf(stderr, " (pid %ld)", holder);
|
||||||
fprintf(stderr, ".\n"
|
fprintf(stderr, ".\n"
|
||||||
"[el] lock: %s\n"
|
"[el] state: %s\n"
|
||||||
|
"[el] lock: %s\n"
|
||||||
"[el] Refusing to start a second instance against the same\n"
|
"[el] Refusing to start a second instance against the same\n"
|
||||||
"[el] state. Stop the running one and VERIFY it is gone\n"
|
"[el] state. Two writers against one store is data loss, not a\n"
|
||||||
"[el] (ps -p <pid>) before retrying.\n",
|
"[el] warning. Stop the running one and VERIFY it is gone\n"
|
||||||
el_singleton_path);
|
"[el] (ps -p %ld) before retrying — or point this instance at a\n"
|
||||||
|
"[el] different state, which is permitted and is not refused.\n",
|
||||||
|
el_singleton_state, el_singleton_path, holder > 0 ? holder : (long)0);
|
||||||
close(fd);
|
close(fd);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1177,7 +1177,7 @@ el_val_t __env_get(el_val_t key);
|
|||||||
* All three are COMPILER-INJECTED at the head of main() — they are not meant to
|
* All three are COMPILER-INJECTED at the head of main() — they are not meant to
|
||||||
* be written by hand, which is the point: the guarantee cannot be forgotten at a
|
* be written by hand, which is the point: the guarantee cannot be forgotten at a
|
||||||
* call site because there is no call site. */
|
* call site because there is no call site. */
|
||||||
el_val_t el_singleton_acquire(el_val_t id); /* §18.1 process identity */
|
el_val_t el_singleton_acquire(el_val_t id, el_val_t state); /* §18.2 process identity — keyed on the guarded state */
|
||||||
el_val_t el_config_declare(el_val_t name, el_val_t type,
|
el_val_t el_config_declare(el_val_t name, el_val_t type,
|
||||||
el_val_t deflt, el_val_t has_default,
|
el_val_t deflt, el_val_t has_default,
|
||||||
el_val_t required); /* §18.2 config schema */
|
el_val_t required); /* §18.2 config schema */
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/* engram_text.c — see engram_text.h.
|
||||||
|
*
|
||||||
|
* Moved verbatim out of el_runtime.c (2026-08-16). Bodies are unchanged; only
|
||||||
|
* `static` was dropped so they link from this translation unit, and each
|
||||||
|
* function's doc comment travelled with it.
|
||||||
|
*/
|
||||||
|
#include "engram_text.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Split q on whitespace into up to ENGRAM_MAX_QTOKENS distinct
|
||||||
|
* (case-insensitive) tokens. Returns the token count. Over-long tokens are
|
||||||
|
* truncated to ENGRAM_QTOK_LEN-1; over-count tokens are ignored. */
|
||||||
|
int engram_tokenize_query(const char* q,
|
||||||
|
char toks[][ENGRAM_QTOK_LEN], int maxtok) {
|
||||||
|
int n = 0;
|
||||||
|
if (!q) return 0;
|
||||||
|
const char* p = q;
|
||||||
|
while (*p && n < maxtok) {
|
||||||
|
while (*p && isspace((unsigned char)*p)) p++;
|
||||||
|
if (!*p) break;
|
||||||
|
char buf[ENGRAM_QTOK_LEN];
|
||||||
|
size_t tl = 0;
|
||||||
|
while (*p && !isspace((unsigned char)*p)) {
|
||||||
|
if (tl < sizeof(buf) - 1) buf[tl++] = *p;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
buf[tl] = '\0';
|
||||||
|
if (tl == 0) continue;
|
||||||
|
int dup = 0;
|
||||||
|
for (int s = 0; s < n; s++) {
|
||||||
|
if (strcasecmp(toks[s], buf) == 0) { dup = 1; break; }
|
||||||
|
}
|
||||||
|
if (dup) continue;
|
||||||
|
memcpy(toks[n], buf, tl + 1);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trim leading/trailing non-alphanumerics, then accept only tokens whose core
|
||||||
|
* is alphanumeric plus '-' and '_' with at least 3 letters. This subsumes the
|
||||||
|
* quoted-title guard (2026-07-25) and the "<!--" flood (2026-08-03)
|
||||||
|
* structurally: markup and punctuation-bearing tokens never become
|
||||||
|
* candidates, rather than being blocklisted after the fact. */
|
||||||
|
int eg_st_clean_token(const char* raw, size_t rawlen,
|
||||||
|
char* out, size_t outcap) {
|
||||||
|
size_t s = 0, e = rawlen;
|
||||||
|
while (s < e && !isalnum((unsigned char)raw[s])) s++;
|
||||||
|
while (e > s && !isalnum((unsigned char)raw[e - 1])) e--;
|
||||||
|
size_t len = e - s;
|
||||||
|
if (len < 4 || len >= outcap) return 0;
|
||||||
|
int alpha = 0;
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
unsigned char c = (unsigned char)raw[s + i];
|
||||||
|
if (isalpha(c)) alpha++;
|
||||||
|
else if (!isdigit(c) && c != '-' && c != '_') return 0;
|
||||||
|
}
|
||||||
|
if (alpha < 3) return 0;
|
||||||
|
memcpy(out, raw + s, len);
|
||||||
|
out[len] = '\0';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Word-boundary document frequency. engram_label_df uses istr_contains, i.e.
|
||||||
|
* SUBSTRING matching, and that is the wrong estimator for term specificity on
|
||||||
|
* short tokens: "them" hits inside "theme" and "anthem", "about" and "whole"
|
||||||
|
* come back with df 2 and 1 rather than 0. That matters here specifically
|
||||||
|
* because the min_df floor is what rejects English function words, and it can
|
||||||
|
* only do that job if their df is honestly zero. Substring df quietly handed
|
||||||
|
* them a survival ticket. Measured on the live store before this fix, "whole"
|
||||||
|
* (df=1, idf=8.76) and "about" (df=2, idf=8.36) were outscoring real topical
|
||||||
|
* terms and losing only on position — one node whose text happened to open
|
||||||
|
* with a function word would have seeded on it.
|
||||||
|
*
|
||||||
|
* engram_label_df keeps substring semantics: it is a separate published
|
||||||
|
* measure with existing callers, and changing it underneath them is not this
|
||||||
|
* change's business. */
|
||||||
|
int eg_st_label_has_word(const char* hay, const char* word) {
|
||||||
|
size_t wl = strlen(word);
|
||||||
|
for (const char* p = hay; *p; p++) {
|
||||||
|
if (strncasecmp(p, word, wl) != 0) continue;
|
||||||
|
char before = (p == hay) ? '\0' : p[-1];
|
||||||
|
char after = p[wl];
|
||||||
|
if (before && (isalnum((unsigned char)before) || before == '_')) continue;
|
||||||
|
if (after && (isalnum((unsigned char)after) || after == '_')) continue;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text-damage signature. Extracted with the function from el_runtime.c's
|
||||||
|
* "Text-integrity instrumentation" block; the stock/flow gauges that use it
|
||||||
|
* (engram_text_health_json, _eg_txt_write_damaged) stay there because they
|
||||||
|
* touch store and EL value types.
|
||||||
|
*
|
||||||
|
* SIGNATURE. Conservative on purpose — a false alarm that cries corruption
|
||||||
|
* over ordinary punctuation is worse than useless. Two patterns, both of
|
||||||
|
* which are essentially absent from well-formed English prose:
|
||||||
|
* (a) alnum '?' alnum — "na?ve", "caf?s", "don?t". A real question mark
|
||||||
|
* never sits between two word characters.
|
||||||
|
* (b) ' ? ' followed by a lowercase letter — a lost em/en dash. A real
|
||||||
|
* question mark is not preceded by a space, and
|
||||||
|
* what follows one starts a new sentence.
|
||||||
|
* Deliberately NOT flagged: a trailing '?' after a word, '? ' before a
|
||||||
|
* capital, or '?' at end of string — all legitimate. This under-counts (it
|
||||||
|
* cannot see a mangled 'café ' where the '?' landed before a space), so the
|
||||||
|
* census is a floor on the damage, never an exaggeration of it. */
|
||||||
|
int eg_text_loss_signature(const char* s) {
|
||||||
|
if (!s) return 0;
|
||||||
|
for (const char* p = s; *p; p++) {
|
||||||
|
if (*p != '?') continue;
|
||||||
|
unsigned char prev = (p == s) ? 0 : (unsigned char)p[-1];
|
||||||
|
unsigned char next = (unsigned char)p[1];
|
||||||
|
/* (a) sandwiched between word characters. */
|
||||||
|
if (isalnum(prev) && isalnum(next)) return 1;
|
||||||
|
/* (b) spaced, with lowercase continuation — a lost dash. */
|
||||||
|
if (prev == ' ' && next == ' ' && islower((unsigned char)p[2])) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/* engram_text.h — text handling for the engram: query tokenization, candidate
|
||||||
|
* token hygiene, word-boundary matching, and the text-damage signature.
|
||||||
|
*
|
||||||
|
* WHY THIS FILE EXISTS
|
||||||
|
* --------------------
|
||||||
|
* These functions lived in el_runtime.c, which is a 2026-05-03 build shim that
|
||||||
|
* was scheduled for deletion, never retired, and grew to 20,527 lines. They do
|
||||||
|
* not belong there: they touch no EL value type and no engram store type. They
|
||||||
|
* are plain C over <ctype.h>/<string.h> operating on char buffers, and they are
|
||||||
|
* a concern of their own — so they get a translation unit of their own.
|
||||||
|
*
|
||||||
|
* Adding a new text helper? Add it HERE, not to el_runtime.c. A new .c costs
|
||||||
|
* exactly one line in lang/runtime/SOURCES, and every build path picks it up.
|
||||||
|
* Placement is a LINK-TIME concern: the compiler cannot tell which .c a symbol
|
||||||
|
* came from (builtin_arity is an arity guard, not a dispatch table), so a
|
||||||
|
* function defined here is exactly as linkable as one defined in el_runtime.c.
|
||||||
|
*/
|
||||||
|
#ifndef ENGRAM_TEXT_H
|
||||||
|
#define ENGRAM_TEXT_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Max bytes per query token, including the NUL. */
|
||||||
|
#define ENGRAM_QTOK_LEN 256
|
||||||
|
|
||||||
|
/* Split q on whitespace into up to ENGRAM_MAX_QTOKENS distinct
|
||||||
|
* (case-insensitive) tokens. Returns the token count. Over-long tokens are
|
||||||
|
* truncated to ENGRAM_QTOK_LEN-1; over-count tokens are ignored. */
|
||||||
|
int engram_tokenize_query(const char* q, char toks[][ENGRAM_QTOK_LEN], int maxtok);
|
||||||
|
|
||||||
|
/* Trim leading/trailing non-alphanumerics, then accept only tokens whose core
|
||||||
|
* is alphanumeric plus '-' and '_' with at least 3 letters. This subsumes the
|
||||||
|
* quoted-title guard (2026-07-25) and the "<!--" flood (2026-08-03)
|
||||||
|
* structurally: markup and punctuation-bearing tokens never become
|
||||||
|
* candidates, rather than being blocklisted after the fact. */
|
||||||
|
int eg_st_clean_token(const char* raw, size_t rawlen, char* out, size_t outcap);
|
||||||
|
|
||||||
|
/* Word-boundary document frequency. engram_label_df uses istr_contains, i.e.
|
||||||
|
* SUBSTRING matching, and that is the wrong estimator for term specificity on
|
||||||
|
* short tokens: "them" hits inside "theme" and "anthem", "about" and "whole"
|
||||||
|
* come back with df 2 and 1 rather than 0. That matters here specifically
|
||||||
|
* because the min_df floor is what rejects English function words, and it can
|
||||||
|
* only do that job if their df is honestly zero. Substring df quietly handed
|
||||||
|
* them a survival ticket. Measured on the live store before this fix, "whole"
|
||||||
|
* (df=1, idf=8.76) and "about" (df=2, idf=8.36) were outscoring real topical
|
||||||
|
* terms and losing only on position — one node whose text happened to open
|
||||||
|
* with a function word would have seeded on it.
|
||||||
|
*
|
||||||
|
* engram_label_df keeps substring semantics: it is a separate published
|
||||||
|
* measure with existing callers, and changing it underneath them is not this
|
||||||
|
* change's business. */
|
||||||
|
int eg_st_label_has_word(const char* hay, const char* word);
|
||||||
|
|
||||||
|
/* Whether s carries the text-loss signature left by the \uXXXX -> '?' parser
|
||||||
|
* defect. Conservative by design; see engram_text.c for the full rationale. */
|
||||||
|
int eg_text_loss_signature(const char* s);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* ENGRAM_TEXT_H */
|
||||||
+47
-8
@@ -697,12 +697,22 @@ Every compiled program links against:
|
|||||||
- `el_runtime.h` — declaration header
|
- `el_runtime.h` — declaration header
|
||||||
- `el_runtime.c` — implementation
|
- `el_runtime.c` — implementation
|
||||||
|
|
||||||
|
The runtime is **multi-file**: `el_runtime.c` `#include`s the six `engram_*.h`
|
||||||
|
headers and calls into all six sibling translation units, so linking it alone
|
||||||
|
fails at `ld`. The canonical link set is `<runtime-dir>/SOURCES`.
|
||||||
|
|
||||||
Compile command:
|
Compile command:
|
||||||
|
|
||||||
```
|
```
|
||||||
cc -std=c11 -I<runtime-dir> -o <prog> <prog>.c el_runtime.c
|
cc -std=c11 -I<runtime-dir> -o <prog> <prog>.c \
|
||||||
|
$(sed 's|^|<runtime-dir>/|' <runtime-dir>/SOURCES) \
|
||||||
|
-lcurl -lssl -lcrypto -lpthread -lm
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Inside this repo, `scripts/el-runtime-sources.sh <runtime-dir>` prints that list
|
||||||
|
(it strips comments; the raw `sed` above works against an installed SDK's
|
||||||
|
`SOURCES`, which `install.sh` writes comment-free).
|
||||||
|
|
||||||
### 13.4 Output Format
|
### 13.4 Output Format
|
||||||
|
|
||||||
```c
|
```c
|
||||||
@@ -1133,6 +1143,7 @@ The `program` block is where a concern of this shape is declared once and enforc
|
|||||||
```
|
```
|
||||||
program "engram" {
|
program "engram" {
|
||||||
singleton: "engram"
|
singleton: "engram"
|
||||||
|
guards: engram_resolve_data_dir()
|
||||||
env ENGRAM_BIND: String = ":8742"
|
env ENGRAM_BIND: String = ":8742"
|
||||||
env GUIDE_PORT: Int = "8771"
|
env GUIDE_PORT: Int = "8771"
|
||||||
env ENGRAM_API_KEY: String required
|
env ENGRAM_API_KEY: String required
|
||||||
@@ -1145,24 +1156,50 @@ Grammar:
|
|||||||
|
|
||||||
```ebnf
|
```ebnf
|
||||||
program_block = "program" string "{" { program_field } "}" ;
|
program_block = "program" string "{" { program_field } "}" ;
|
||||||
program_field = singleton_field | env_field ;
|
program_field = singleton_field | guards_field | env_field ;
|
||||||
singleton_field = "singleton" ":" string [ "," ] ;
|
singleton_field = "singleton" ":" string [ "," ] ;
|
||||||
|
guards_field = "guards" ":" expr [ "," ] ;
|
||||||
env_field = "env" ident ":" type
|
env_field = "env" ident ":" type
|
||||||
[ "=" string ] [ "required" ] [ "," ] ;
|
[ "=" string ] [ "required" ] [ "," ] ;
|
||||||
```
|
```
|
||||||
|
|
||||||
`singleton` and `env` are **not** reserved words. They are read as identifier token values by the block's own parse loop, so they remain usable as ordinary identifiers everywhere else. `program` is the only keyword this section adds.
|
`singleton`, `guards` and `env` are **not** reserved words. They are read as identifier token values by the block's own parse loop, so they remain usable as ordinary identifiers everywhere else. `program` is the only keyword this section adds.
|
||||||
|
|
||||||
### 18.2 Process identity — `singleton`
|
### 18.2 Process identity — `singleton` and `guards`
|
||||||
|
|
||||||
`singleton: "id"` compiles to an `el_singleton_acquire("id")` call injected as the **first statement of `main()`**, before any user statement runs.
|
`singleton: "id"` with `guards: <expr>` compiles to `el_singleton_acquire("id", <expr>)`, injected as the **first statement of `main()`**, before any user statement runs. `<expr>` evaluates to the path of the **state** the singleton protects.
|
||||||
|
|
||||||
The runtime takes an exclusive non-blocking `flock` on `<dir>/el-singleton-<id>.lock`, where `<dir>` is `$EL_SINGLETON_DIR`, else `$TMPDIR`, else `/tmp`. On success it writes its pid and holds the descriptor open for the life of the process. On contention it **refuses to start**: it reports the holder's pid, names the lock file, and exits 1.
|
**`guards:` is mandatory.** A `singleton:` without one is a compile error. This is not defensive strictness; it is the correction of a defect measured in this tree on 2026-08-16, and the rule the rest of this section exists to state:
|
||||||
|
|
||||||
Two properties are deliberate:
|
> **Guard the thing, not the name.** A lock that protects state must be keyed on the state.
|
||||||
|
|
||||||
- **It is a lock, not a pidfile.** The kernel releases an `flock` when the owning process dies — including on `SIGKILL` and on crash. There is therefore no stale-lock state, and so no "delete the lock file to get unstuck" recovery ritual. Such a ritual would itself be a convention, which is the thing this section exists to remove.
|
Until that date the lock was `<dir>/el-singleton-<id>.lock` where `<dir>` was `$EL_SINGLETON_DIR`, else `$TMPDIR`, else `/tmp`. It was keyed on the program's **name** and on a temp directory, and it never consulted the state it claimed to protect — while its own refusal message read *"Refusing to start a second instance against the same state."* Measured, it failed in **both** directions:
|
||||||
|
|
||||||
|
| Situation | Correct answer | Name-keyed lock gave |
|
||||||
|
|---|---|---|
|
||||||
|
| same data dir, same `$TMPDIR` | refuse | refuse ✅ |
|
||||||
|
| same data dir, different `$TMPDIR` | refuse | **started** ❌ — the two-writer data-loss condition, defeated by one environment variable |
|
||||||
|
| different data dirs, same `$TMPDIR` | both start | **refused**, naming an unrelated pid ❌ |
|
||||||
|
| same dir spelled differently, different `$TMPDIR` | refuse | **started** ❌ |
|
||||||
|
|
||||||
|
Both failure directions are one error: the identity of a resource had been replaced by a label for it. The false negative is the dangerous one — a guard whose bypass is `TMPDIR=/tmp/other` is not a guard.
|
||||||
|
|
||||||
|
**The mechanism.** The lock file lives **inside the guarded directory**: `<state>/.el-singleton-<id>.lock`. The runtime takes an exclusive non-blocking `flock` on it, writes its pid, and holds the descriptor open for the life of the process.
|
||||||
|
|
||||||
|
That single placement decision is the whole fix, and it is why there is no hashing, no canonical-path registry, and no environment variable left to subvert:
|
||||||
|
|
||||||
|
- **Same directory** ⇒ same file ⇒ same inode ⇒ the `flock` contends. `$TMPDIR` is not in the key, so there is nothing to change to get past it. `$EL_SINGLETON_DIR` no longer exists.
|
||||||
|
- **Different directories** ⇒ different files ⇒ no contention. Two stores are two stores; they were never in conflict, and are no longer treated as if they were.
|
||||||
|
- **Different spellings of one directory** — trailing slash, `x/../x`, a symlink — resolve to the same inode during the kernel's own path walk, so they contend without this code comparing strings. Path canonicalisation happens only to make the diagnostic name one directory in one spelling; the *decision* never depends on it.
|
||||||
|
- **An unguardable state** — the directory is missing, or read-only — is a **refusal**, not a fallback. Starting unguarded against the store the guard exists to protect is the failure being removed.
|
||||||
|
|
||||||
|
**Why `guards:` is an expression and not a string.** The runtime cannot know, generically, which environment variable holds an arbitrary program's state; and a program whose state path already has an owner must not restate it. The engram's data dir is resolved by `engram_resolve_data_dir()`, which owns both the `$ENGRAM_DATA_DIR` read and the `$HOME/.neuron/engram` fallback (§18.4). Writing `guards: engram_resolve_data_dir()` points the guard at that owner. A `guards:` that took a string would force the path's default to be written down twice, and a guard that resolved the path its own way could end up locking a directory the program never writes to — the same two-owners defect §18.4 exists to prevent.
|
||||||
|
|
||||||
|
Three properties are deliberate:
|
||||||
|
|
||||||
|
- **It is a lock, not a pidfile.** The kernel releases an `flock` when the owning process dies — including on `SIGKILL` and on crash. There is therefore no stale-lock state, and so no "delete the lock file to get unstuck" recovery ritual. Such a ritual would itself be a convention, which is the thing this section exists to remove. (A lock file left behind inside a copied data directory — `cp -Rc` and friends — is inert: it carries no lock, only a stale pid string that the next holder overwrites.)
|
||||||
- **It reports the holder's pid.** "Already running" is not actionable. A pid is. This is the direct answer to the observed failure where a stale process survived a `pkill` and went on answering probes.
|
- **It reports the holder's pid.** "Already running" is not actionable. A pid is. This is the direct answer to the observed failure where a stale process survived a `pkill` and went on answering probes.
|
||||||
|
- **The message is true.** It names the state it checked and the lock it failed to take, and it says "the same state" only because the lock it contended for is *in* that state. A diagnostic that asserts a check that did not happen is worse than no diagnostic: it is what let the name-keyed version read as correct for as long as it did.
|
||||||
|
|
||||||
Refusal is loud and total. It is not a warning, and the program does not continue degraded. This matters more than it looks: today a second engram whose `bind()` fails merely *returns* from `http_serve` — after it has already replayed the WAL and written boot-time backup files — and then exits **0**, indistinguishable from a clean run. `singleton` refuses before the first side effect.
|
Refusal is loud and total. It is not a warning, and the program does not continue degraded. This matters more than it looks: today a second engram whose `bind()` fails merely *returns* from `http_serve` — after it has already replayed the WAL and written boot-time backup files — and then exits **0**, indistinguishable from a clean run. `singleton` refuses before the first side effect.
|
||||||
|
|
||||||
@@ -1184,6 +1221,8 @@ Some values look like configuration and are not. `ENGRAM_DATA_DIR` already has a
|
|||||||
|
|
||||||
The rule: **a variable belongs in the program block when the block would be its only owner.** If a resolver already owns it, leave it there.
|
The rule: **a variable belongs in the program block when the block would be its only owner.** If a resolver already owns it, leave it there.
|
||||||
|
|
||||||
|
This is also why `guards:` (§18.2) takes an expression: it lets the block *reference* the existing owner — `guards: engram_resolve_data_dir()` — rather than become a second one.
|
||||||
|
|
||||||
`HOME` is likewise not configuration. It is an environment fact, and stays a raw `env()` read.
|
`HOME` is likewise not configuration. It is an environment fact, and stays a raw `env()` read.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Executable
+161
@@ -0,0 +1,161 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# check-runtime-growth.sh — GROWTH guard for lang/runtime/el_runtime.c.
|
||||||
|
#
|
||||||
|
# Sibling to scripts/check-single-runtime.sh. That one guards against the file
|
||||||
|
# being COPIED (a lagging fork shipped to prod and dropped learned hebb edges).
|
||||||
|
# Nothing guarded against it GROWING — so it grew from 10,607 to 20,527 lines in
|
||||||
|
# 3.5 months, while under an explicit commit-message promise that it was a
|
||||||
|
# temporary shim about to be deleted.
|
||||||
|
#
|
||||||
|
# This enforces the RATCHET in lang/runtime/BUDGET: the numbers may only go down.
|
||||||
|
#
|
||||||
|
# It also checks two invariants that keep the multi-file runtime honest:
|
||||||
|
# * every .c in lang/runtime/ is either in SOURCES or explicitly optional
|
||||||
|
# * lang/install.sh's hardcoded download list matches SOURCES
|
||||||
|
#
|
||||||
|
# Exits non-zero on any violation. Run from anywhere; resolves the repo root.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
RUNTIME_DIR="lang/runtime"
|
||||||
|
TARGET="$RUNTIME_DIR/el_runtime.c"
|
||||||
|
BUDGET_FILE="$RUNTIME_DIR/BUDGET"
|
||||||
|
SOURCES_FILE="$RUNTIME_DIR/SOURCES"
|
||||||
|
|
||||||
|
FAIL=0
|
||||||
|
|
||||||
|
for f in "$TARGET" "$BUDGET_FILE" "$SOURCES_FILE"; do
|
||||||
|
if [ ! -f "$f" ]; then
|
||||||
|
echo "FATAL: required file missing: $f" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
budget() {
|
||||||
|
local key="$1"
|
||||||
|
sed -e 's/#.*//' "$BUDGET_FILE" | awk -v k="$key" '$1==k {print $2; found=1} END{if(!found) exit 1}'
|
||||||
|
}
|
||||||
|
|
||||||
|
MAX_LINES="$(budget max_lines)" || { echo "FATAL: no 'max_lines' in $BUDGET_FILE" >&2; exit 1; }
|
||||||
|
MAX_ENGRAM="$(budget max_engram_fns)" || { echo "FATAL: no 'max_engram_fns' in $BUDGET_FILE" >&2; exit 1; }
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The message every failure prints. The guard that existed before this one told
|
||||||
|
# you what was wrong but not where the code should go — so it was easy to
|
||||||
|
# "fix" by arguing with the guard. This one names the destination.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
where_it_goes() {
|
||||||
|
cat >&2 <<'MSG'
|
||||||
|
|
||||||
|
WHERE THE CODE ACTUALLY GOES
|
||||||
|
----------------------------
|
||||||
|
Placement is a LINK-TIME concern. The compiler cannot tell which .c a symbol
|
||||||
|
came from: `builtin_arity` in el-compiler/src/codegen.el maps NAME -> ARITY
|
||||||
|
INT only, the El name is emitted as the exact C symbol, and `ld` resolves it.
|
||||||
|
The SHIPPED compiler already links from ten translation units — check it:
|
||||||
|
|
||||||
|
nm lang/dist/platform/elc | grep -E 'T _(engram_think|vindex_insert)'
|
||||||
|
|
||||||
|
So a builtin defined in a sibling .c is EXACTLY as linkable as one defined in
|
||||||
|
el_runtime.c. Pick the file that owns the concern:
|
||||||
|
|
||||||
|
engram store ops ......... lang/runtime/engram_store.c
|
||||||
|
ANN / vector index ....... lang/runtime/engram_vindex.c
|
||||||
|
geometry, priming ........ lang/runtime/engram_geometry.c
|
||||||
|
reasoning operators ...... lang/runtime/engram_reason.c
|
||||||
|
grounding, consistency ... lang/runtime/engram_verify.c
|
||||||
|
think, stance ............ lang/runtime/engram_cognition.c
|
||||||
|
|
||||||
|
No existing file owns it? Create one, add ONE line to lang/runtime/SOURCES,
|
||||||
|
and every build path picks it up. Every runtime file EXCEPT el_runtime.c is
|
||||||
|
deliberately uncapped.
|
||||||
|
|
||||||
|
Belongs to a downstream program, not the runtime? Declare
|
||||||
|
`c_source "path/to/file.c"` in that program's manifest.el — elb already links
|
||||||
|
it (parse_manifest_c_sources, lang/elb.el:82).
|
||||||
|
|
||||||
|
See lang/AGENTS.md "Where a new C builtin goes".
|
||||||
|
MSG
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1. Line-count ratchet ---------------------------------------------------
|
||||||
|
LINES="$(wc -l < "$TARGET" | tr -d ' ')"
|
||||||
|
if [ "$LINES" -gt "$MAX_LINES" ]; then
|
||||||
|
echo "FAIL: $TARGET grew past its budget." >&2
|
||||||
|
echo " now: $LINES lines" >&2
|
||||||
|
echo " budget: $MAX_LINES lines (lang/runtime/BUDGET: max_lines)" >&2
|
||||||
|
echo " over by: $((LINES - MAX_LINES))" >&2
|
||||||
|
echo "" >&2
|
||||||
|
echo "This file is a 2026-05-03 build shim that was scheduled for deletion and" >&2
|
||||||
|
echo "never retired. It does not get to grow. Do NOT raise the budget." >&2
|
||||||
|
where_it_goes
|
||||||
|
FAIL=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 2. Engram-concern ratchet ----------------------------------------------
|
||||||
|
# ~47.5% of el_runtime.c is engram code, and engram already owns six sibling
|
||||||
|
# files. This count is the Stage 3 scoreboard: it may only go down.
|
||||||
|
ENGRAM_FNS="$(grep -cE '^(static +)?[A-Za-z_][A-Za-z0-9_ *]*\b(engram|eg|cog)_[a-z0-9_]+\(' "$TARGET" || true)"
|
||||||
|
if [ "$ENGRAM_FNS" -gt "$MAX_ENGRAM" ]; then
|
||||||
|
echo "FAIL: new engram/eg_/cog_ function(s) added to $TARGET." >&2
|
||||||
|
echo " now: $ENGRAM_FNS definitions" >&2
|
||||||
|
echo " budget: $MAX_ENGRAM (lang/runtime/BUDGET: max_engram_fns)" >&2
|
||||||
|
echo "" >&2
|
||||||
|
echo "Engram code belongs in the six engram_*.c files that already exist." >&2
|
||||||
|
where_it_goes
|
||||||
|
FAIL=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 3. Ratchet-down nudge (advisory, never fails) ---------------------------
|
||||||
|
if [ "$LINES" -lt "$MAX_LINES" ]; then
|
||||||
|
echo "NOTE: $TARGET is $((MAX_LINES - LINES)) lines under budget — lower" >&2
|
||||||
|
echo " 'max_lines' to $LINES in $BUDGET_FILE in this same commit, so the" >&2
|
||||||
|
echo " ground you gained cannot be quietly given back." >&2
|
||||||
|
fi
|
||||||
|
if [ "$ENGRAM_FNS" -lt "$MAX_ENGRAM" ]; then
|
||||||
|
echo "NOTE: $((MAX_ENGRAM - ENGRAM_FNS)) engram fn(s) moved out — lower" >&2
|
||||||
|
echo " 'max_engram_fns' to $ENGRAM_FNS in $BUDGET_FILE in this same commit." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 4. Every runtime .c is accounted for ------------------------------------
|
||||||
|
# A new .c that is in neither SOURCES nor the optional list will not be
|
||||||
|
# compiled by any build path — it would be silently dead. Catch that here.
|
||||||
|
OPTIONAL_RE='^(el_android|el_gtk4|el_lvgl|el_sdl2|el_win32|el_runtime_win32|eg_cosine_batch_strategy_ggml|vindex_bench)\.c$'
|
||||||
|
mapfile -t IN_SOURCES < <(scripts/el-runtime-sources.sh)
|
||||||
|
for path in "$RUNTIME_DIR"/*.c; do
|
||||||
|
base="$(basename "$path")"
|
||||||
|
if printf '%s\n' "${IN_SOURCES[@]}" | grep -qxF "$base"; then continue; fi
|
||||||
|
if [[ "$base" =~ $OPTIONAL_RE ]]; then continue; fi
|
||||||
|
echo "FAIL: $path is in neither lang/runtime/SOURCES nor the platform-optional" >&2
|
||||||
|
echo " list in this guard. It will not be compiled by any build path." >&2
|
||||||
|
echo " Add it to SOURCES (one line), or add it to OPTIONAL_RE here if it" >&2
|
||||||
|
echo " is a platform/strategy variant that is linked in deliberately." >&2
|
||||||
|
FAIL=1
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- 5. install.sh must not drift from SOURCES -------------------------------
|
||||||
|
# install.sh runs on machines with no repo checkout, so it cannot call
|
||||||
|
# el-runtime-sources.sh and has to hardcode the list. That copy is exactly the
|
||||||
|
# kind of duplicate that silently drifted before — so it is checked, not trusted.
|
||||||
|
INSTALL_SH="lang/install.sh"
|
||||||
|
if [ -f "$INSTALL_SH" ]; then
|
||||||
|
EXPECTED="$(scripts/el-runtime-sources.sh | sort)"
|
||||||
|
ACTUAL="$(sed -n '/^RUNTIME_SOURCES=(/,/^)/p' "$INSTALL_SH" \
|
||||||
|
| grep -oE '[a-z_0-9]+\.c' | sort)"
|
||||||
|
if [ "$EXPECTED" != "$ACTUAL" ]; then
|
||||||
|
echo "FAIL: $INSTALL_SH RUNTIME_SOURCES has drifted from $SOURCES_FILE." >&2
|
||||||
|
echo " Only in SOURCES: $(comm -23 <(echo "$EXPECTED") <(echo "$ACTUAL") | tr '\n' ' ')" >&2
|
||||||
|
echo " Only in install.sh: $(comm -13 <(echo "$EXPECTED") <(echo "$ACTUAL") | tr '\n' ' ')" >&2
|
||||||
|
echo " An SDK that ships the wrong set produces a lib/ that cannot link." >&2
|
||||||
|
FAIL=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$FAIL" -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK: el_runtime.c within budget ($LINES/$MAX_LINES lines, $ENGRAM_FNS/$MAX_ENGRAM engram fns);"
|
||||||
|
echo " runtime sources accounted for; install.sh in step with SOURCES."
|
||||||
@@ -81,14 +81,14 @@ fi
|
|||||||
echo "OK: single canonical runtime source — $CANONICAL (no un-allowlisted forks)."
|
echo "OK: single canonical runtime source — $CANONICAL (no un-allowlisted forks)."
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# CI wire-in:
|
# CI wire-in — DONE (2026-08-16). This block used to describe the wire-in as a
|
||||||
# foundation/el .gitea/workflows/ci-dev.yaml, ci-stage.yaml, sdk-release.yaml
|
# TODO, and it had never been done: the guard existed but ran nowhere, so it
|
||||||
# Add an early step (before the build/publish steps). It must run from the
|
# caught nothing for as long as it has been in the tree. It is now an early step
|
||||||
# REPO ROOT, so override the job's `defaults.run.working-directory: lang`:
|
# in ci-dev.yaml, ci-stage.yaml and sdk-release.yaml (each with
|
||||||
|
# `working-directory: ${{ github.workspace }}`, since the jobs default to lang/),
|
||||||
|
# and it runs in .githooks/pre-commit.
|
||||||
#
|
#
|
||||||
# - name: Guard - single canonical runtime source
|
# Its sibling scripts/check-runtime-growth.sh is wired in at the same points and
|
||||||
# working-directory: ${{ github.workspace }}
|
# guards the other half of the problem: this script stops el_runtime.c being
|
||||||
# run: bash scripts/check-single-runtime.sh
|
# COPIED, that one stops it GROWING.
|
||||||
#
|
|
||||||
# Also add to .githooks/pre-commit so drift is caught before it is committed.
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Executable
+73
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# el-runtime-sources.sh — print the canonical El runtime link set.
|
||||||
|
#
|
||||||
|
# Reads lang/runtime/SOURCES (the single source of truth) and prints one path
|
||||||
|
# per line, optionally prefixed with a directory. Use it anywhere a link line
|
||||||
|
# would otherwise spell the runtime .c files out longhand:
|
||||||
|
#
|
||||||
|
# cc -std=c11 -O2 -I lang/runtime -o app app.c \
|
||||||
|
# $(scripts/el-runtime-sources.sh lang/runtime) \
|
||||||
|
# -lcurl -lssl -lcrypto -lpthread -lm
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --headers print the shipped headers instead of the .c sources
|
||||||
|
# --check verify every listed file exists; exit non-zero if any is missing
|
||||||
|
#
|
||||||
|
# WHY: linking el_runtime.c alone has been broken since el_runtime.c started
|
||||||
|
# calling into the engram siblings. The list was duplicated across ~8 build
|
||||||
|
# paths and drifted. It lives in exactly one place now — see lang/runtime/SOURCES.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
SOURCES="${ROOT}/lang/runtime/SOURCES"
|
||||||
|
|
||||||
|
if [ ! -f "$SOURCES" ]; then
|
||||||
|
echo "FATAL: canonical runtime source list missing: $SOURCES" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODE="sources"
|
||||||
|
PREFIX=""
|
||||||
|
CHECK=0
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--headers) MODE="headers" ;;
|
||||||
|
--check) CHECK=1 ;;
|
||||||
|
-*) echo "el-runtime-sources.sh: unknown option: $arg" >&2; exit 2 ;;
|
||||||
|
*) PREFIX="${arg%/}/" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Strip comments and blank lines. Order is preserved — it is link order.
|
||||||
|
mapfile -t FILES < <(sed -e 's/#.*//' -e 's/[[:space:]]*$//' "$SOURCES" | grep -v '^$')
|
||||||
|
|
||||||
|
if [ "${#FILES[@]}" -eq 0 ]; then
|
||||||
|
echo "FATAL: $SOURCES lists no sources" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$MODE" = "headers" ]; then
|
||||||
|
# Every .c's matching .h, plus the headers that carry no .c of their own.
|
||||||
|
HDRS=()
|
||||||
|
for f in "${FILES[@]}"; do
|
||||||
|
h="${f%.c}.h"
|
||||||
|
[ -f "${ROOT}/lang/runtime/${h}" ] && HDRS+=("$h")
|
||||||
|
done
|
||||||
|
# Interface-only headers: no matching .c, but required to compile against.
|
||||||
|
for h in eg_cosine_batch_strategy.h el_native_target.h el_platform_win.h; do
|
||||||
|
[ -f "${ROOT}/lang/runtime/${h}" ] && HDRS+=("$h")
|
||||||
|
done
|
||||||
|
FILES=("${HDRS[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
RC=0
|
||||||
|
for f in "${FILES[@]}"; do
|
||||||
|
if [ "$CHECK" -eq 1 ] && [ ! -f "${ROOT}/lang/runtime/${f}" ]; then
|
||||||
|
echo "MISSING: lang/runtime/${f} (listed in lang/runtime/SOURCES)" >&2
|
||||||
|
RC=1
|
||||||
|
fi
|
||||||
|
printf '%s%s\n' "$PREFIX" "$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
exit $RC
|
||||||
Reference in New Issue
Block a user