diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 8508781..4d6a9dc 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -63,6 +63,16 @@ jobs: cp vendor/el-runtime/v1.0.0-20260501/el_runtime.h /opt/el/runtime/el_runtime.h echo "El runtime PINNED to v1.0.0-20260501: $(ls /opt/el/runtime/)" + # neuron#133: CI compiles dist/soul.c, NOT the .el sources. On 2026-08-07 a + # build off main would have shipped an engine with none of five merged fixes, + # including a P0 safety fix, while main's source read as correct. The runner + # cannot regenerate the amalgam (elc needs 24GB+ virtual memory), but it can + # refuse to compile a stale one. Fails loudly with the recipe in the message. + - name: Verify dist/soul.c matches the sources + run: | + chmod +x tools/soulc-stamp.sh + ./tools/soulc-stamp.sh --check + - name: Build neuron soul binary run: | RUNTIME=/opt/el/runtime diff --git a/dist/soul.c b/dist/soul.c index bb7814c..b864151 100644 --- a/dist/soul.c +++ b/dist/soul.c @@ -974,6 +974,10 @@ el_val_t wt_commit(el_val_t id); el_val_t tier_working(void); el_val_t tier_episodic(void); el_val_t tier_canonical(void); +el_val_t mem_assoc_skip_label(el_val_t label); +el_val_t mem_assoc_ok(el_val_t cand_id, el_val_t cand_label, el_val_t self_id); +el_val_t mem_assoc_slot(el_val_t results, el_val_t idx, el_val_t new_id); +el_val_t mem_associate(el_val_t new_id, el_val_t content, el_val_t label); el_val_t mem_store(el_val_t content, el_val_t label, el_val_t tags); el_val_t mem_remember(el_val_t content, el_val_t tags); el_val_t mem_recall(el_val_t query, el_val_t depth); @@ -25573,6 +25577,97 @@ el_val_t tier_canonical(void) { return 0; } +el_val_t mem_assoc_skip_label(el_val_t label) { + if (str_contains(label, EL_STR("state-event"))) { + return 1; + } + if (str_contains(label, EL_STR("soul-response"))) { + return 1; + } + if (str_contains(label, EL_STR("soul-outbox"))) { + return 1; + } + if (str_contains(label, EL_STR("boot_count"))) { + return 1; + } + if (str_contains(label, EL_STR("loop-outcome"))) { + return 1; + } + if (str_contains(label, EL_STR("search-result"))) { + return 1; + } + return 0; + return 0; +} + +el_val_t mem_assoc_ok(el_val_t cand_id, el_val_t cand_label, el_val_t self_id) { + if (str_eq(cand_id, EL_STR(""))) { + return 0; + } + if (str_eq(cand_id, self_id)) { + return 0; + } + el_val_t lab = str_lower(cand_label); + if (str_starts_with(lab, EL_STR("self"))) { + return 0; + } + if (str_starts_with(lab, EL_STR("value"))) { + return 0; + } + if (str_contains(lab, EL_STR("values"))) { + return 0; + } + if (str_contains(lab, EL_STR("identity"))) { + return 0; + } + if (mem_assoc_skip_label(cand_label)) { + return 0; + } + return 1; + return 0; +} + +el_val_t mem_assoc_slot(el_val_t results, el_val_t idx, el_val_t new_id) { + if (idx >= json_array_len(results)) { + return 0; + } + el_val_t cand = json_array_get(results, idx); + el_val_t cid = json_get(cand, EL_STR("id")); + el_val_t clabel = json_get(cand, EL_STR("label")); + el_val_t ctype = json_get(cand, EL_STR("node_type")); + if (str_eq(ctype, EL_STR("Value"))) { + return 0; + } + if (str_eq(ctype, EL_STR("DharmaSelf"))) { + return 0; + } + if (str_eq(ctype, EL_STR("Safety"))) { + return 0; + } + if (mem_assoc_ok(cid, clabel, new_id)) { + wt_edge(new_id, cid, el_from_float(0.5), EL_STR("related")); + } + return 0; +} + +el_val_t mem_associate(el_val_t new_id, el_val_t content, el_val_t label) { + if (str_eq(new_id, EL_STR(""))) { + return 0; + } + if (mem_assoc_skip_label(label)) { + return 0; + } + el_val_t probe = str_slice(content, 0, 400); + el_val_t results = engram_recall_json(probe, 4); + if (str_eq(results, EL_STR(""))) { + return 0; + } + mem_assoc_slot(results, 0, new_id); + mem_assoc_slot(results, 1, new_id); + mem_assoc_slot(results, 2, new_id); + return 0; +} + el_val_t mem_store(el_val_t content, el_val_t label, el_val_t tags) { el_val_t id = wt_node(content, EL_STR("Memory"), label, el_from_float(0.5), el_from_float(0.5), el_from_float(0.8), EL_STR("Working"), tags); if (str_eq(id, EL_STR(""))) { @@ -25580,6 +25675,7 @@ el_val_t mem_store(el_val_t content, el_val_t label, el_val_t tags) { return EL_STR(""); } el_val_t durable = wt_commit(id); + mem_associate(id, content, label); if (durable) { println(el_str_concat(el_str_concat(el_str_concat(EL_STR("[memory] write persisted at owner: "), id), EL_STR(" label=")), label)); } else { @@ -29709,7 +29805,9 @@ el_val_t handle_api_begin_session(el_val_t body) { el_val_t state_events = api_compact_node_array(state_events_raw, 5, 500); el_val_t recent_raw = engram_scan_nodes_json(10, 0); el_val_t recent = api_compact_node_array(recent_raw, 10, 240); - return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"stats\":"), stats), EL_STR(",\"recent\":")), recent), EL_STR(",\"activated\":")), activated), EL_STR(",\"self_neighbors\":[]")), EL_STR(",\"recent_state_events\":")), state_events), EL_STR("}")); + el_val_t self_raw = engram_neighbors_json(EL_STR("kn-efeb4a5b-5aff-4759-8a97-7233099be6ee"), 1, EL_STR("both")); + el_val_t self_slice = api_compact_node_array(self_raw, 24, 240); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"stats\":"), stats), EL_STR(",\"recent\":")), recent), EL_STR(",\"activated\":")), activated), EL_STR(",\"self_neighbors\":")), self_slice), EL_STR(",\"recent_state_events\":")), state_events), EL_STR("}")); return 0; } @@ -29739,6 +29837,7 @@ el_val_t handle_api_remember(el_val_t body) { if (!api_persisted(id)) { return api_not_persisted(id); } + mem_associate(id, content, EL_STR("memory:remembered")); return el_str_concat(el_str_concat(EL_STR("{\"id\":\""), id), EL_STR("\",\"ok\":true}")); return 0; } @@ -29830,7 +29929,7 @@ el_val_t handle_api_recall(el_val_t method, el_val_t path, el_val_t body) { if (str_eq(eff_q, EL_STR(""))) { return api_or_empty(engram_scan_nodes_json(limit, 0)); } - el_val_t results = engram_search_json(eff_q, limit); + el_val_t results = engram_recall_json(eff_q, limit); return api_or_empty(results); return 0; } diff --git a/dist/soul.c.stamp b/dist/soul.c.stamp new file mode 100644 index 0000000..7bbbf80 --- /dev/null +++ b/dist/soul.c.stamp @@ -0,0 +1,18 @@ +# soul.c.stamp — fingerprint of the .el sources dist/soul.c was generated from. +# Written by tools/soulc-stamp.sh --write. Do not hand-edit. +# generated_amalgam_sha256 e31f760de5f6629cd37bcfd26172f92be3ca5d6ff36480f6adafa1d4a1de5a66 +# generated_amalgam_bytes 1179122 +f8597e10546654bce3fbbe40461b2da59d0e06dbf1b038d1d362d24f949e3911 awareness.el +b6f3d14ca0c26017a2d617399a6d3754dabb0905e4d5f52eb75d25c4ad18d3c5 chat.el +42288c212cbf72fb1e8ecbd4d9900e4e9ee1cfa475b7974295c7637f1bf2939f elp-input.el +b3f77f49d6086932c38bd17fe7a5eaf8bce25685f6fc3e1750f05729c6b49b9e imprint.el +fba8ffdb9ba72bca5b09ca1c93a520edc52f3f4d8aec2c7585fe9b17e06420b2 manifest.el +550a72e234ae8cec1f33e02108fd365353f45edd88513da90b792e79b6c0e5f0 memory.el +77640783df5c38066149dcd11a11aea7e36f66e40e3df6aedf6723be8b9e0e1e neuron-api.el +03c47c451e0e87f2c252cadb4b765867943962a804f548dd53adeef0520912c8 persist.el +541b9309c59c33ee619d393714871b820ec4151b721d8b2aebcfdb7ce8eb2ffa routes.el +c28e36952ec56525963a0bdf29455ab097d3b0c5653d19c25fbb005e1069a1f7 safety.el +fd3ab91d0ae0ea26639e21bef2f8f94054dc4b02eae68b19e3fe689d2769aad4 sessions.el +0f1cf43904a98a5a646cce5a07e0e96162ced662692fbc13357d9b67d9a8ac3d soul.el +30337940905171a9645b0929f0a412ce6b3dccb1246495070c553bca0bbae6cd stewardship.el +e105dc5990e6adbf39db9dc0462cd8bcf6e6c3dfd03709059227ecfad2bbab29 studio.el diff --git a/tools/soulc-stamp.sh b/tools/soulc-stamp.sh new file mode 100755 index 0000000..de83273 --- /dev/null +++ b/tools/soulc-stamp.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# soulc-stamp.sh — make it impossible for dist/soul.c to drift from the sources +# in silence. +# +# THE PROBLEM (neuron#133, and its own words): "Nothing in the tree regenerates +# this file. Only a human running the recipe. It lags in batches, never +# per-change, and it will drift again." +# +# It drifted. On 2026-08-07 a CI or GKE build off main would have shipped an +# engine with NONE of five merged fixes — including a P0 safety fix — while +# main's source read as correct. CI compiles dist/soul.c, not the .el files, so +# the source being right is not the same as the build being right. +# +# WHY A STAMP AND NOT AUTO-REGENERATION: the CI workflow says elc cannot run on +# the runner ("elb on Linux would OOM the runner (elc uses 24GB+ virtual memory +# on a 16GB host)"). So the build cannot regenerate the file itself. What it CAN +# do, for free and with no compiler, is refuse to compile a stale one. +# +# The stamp records a fingerprint of every .el source that feeds the amalgam at +# the moment it was generated. --check recomputes and compares. Divergence is a +# build failure with the recipe in the message, not a silent ship. +# +# soulc-stamp.sh --write after regenerating dist/soul.c (records the fingerprint) +# soulc-stamp.sh --check in CI, before the compile (fails on drift) +set -u +MODE="${1:---check}" +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +STAMP="$ROOT/dist/soul.c.stamp" +AMALGAM="$ROOT/dist/soul.c" + +# Every .el at the repo root is an input to the amalgam. Sorted so the hash is +# order-independent; content-only so timestamps and checkouts do not perturb it. +fingerprint() { + ( + cd "$ROOT" || exit 1 + for f in $(ls -1 *.el 2>/dev/null | sort); do + printf '%s %s\n' "$(shasum -a 256 "$f" | awk '{print $1}')" "$f" + done + ) +} + +case "$MODE" in + --write) + [ -f "$AMALGAM" ] || { echo "no dist/soul.c to stamp — regenerate it first" >&2; exit 2; } + { + echo "# soul.c.stamp — fingerprint of the .el sources dist/soul.c was generated from." + echo "# Written by tools/soulc-stamp.sh --write. Do not hand-edit." + echo "# generated_amalgam_sha256 $(shasum -a 256 "$AMALGAM" | awk '{print $1}')" + echo "# generated_amalgam_bytes $(wc -c < "$AMALGAM" | tr -d ' ')" + fingerprint + } > "$STAMP" + echo "stamped $(fingerprint | wc -l | tr -d ' ') sources -> dist/soul.c.stamp" + ;; + + --check) + if [ ! -f "$STAMP" ]; then + echo "FAIL: dist/soul.c.stamp is missing — the build input is unverifiable." >&2 + echo " Regenerate the amalgam, then: tools/soulc-stamp.sh --write" >&2 + exit 1 + fi + RECORDED="$(grep -v '^#' "$STAMP")" + CURRENT="$(fingerprint)" + if [ "$RECORDED" = "$CURRENT" ]; then + echo "soulc-stamp: OK — dist/soul.c matches the .el sources" + exit 0 + fi + echo "FAIL: dist/soul.c is STALE. It does not match the current .el sources." >&2 + echo "" >&2 + echo "CI compiles dist/soul.c, not the .el files. Shipping this means shipping" >&2 + echo "an engine that does not contain the merged source. That is neuron#133," >&2 + echo "which once hid five merged fixes including a P0 safety fix." >&2 + echo "" >&2 + echo "Sources that changed since the amalgam was generated:" >&2 + diff <(printf '%s\n' "$RECORDED") <(printf '%s\n' "$CURRENT") \ + | grep -E '^[<>]' | awk '{print " " $1 " " $3}' | sort -u >&2 + echo "" >&2 + echo "Fix: regenerate the amalgam, then tools/soulc-stamp.sh --write" >&2 + exit 1 + ;; + + *) + echo "usage: soulc-stamp.sh [--check|--write]" >&2; exit 2 ;; +esac