Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89e45ed689 |
@@ -7300,13 +7300,48 @@ el_val_t engram_save(el_val_t path) {
|
|||||||
jb_putc(&b, '}');
|
jb_putc(&b, '}');
|
||||||
}
|
}
|
||||||
jb_puts(&b, "]}");
|
jb_puts(&b, "]}");
|
||||||
FILE* f = fopen(p, "wb");
|
|
||||||
if (!f) { free(b.buf); return 0; }
|
/* --- Anti-clobber sparse-write floor (NTN engram clobber fix) ---------
|
||||||
|
* Refuse to overwrite an existing populated snapshot with a drastically
|
||||||
|
* smaller one. A bad boot that loaded only ~63 identity nodes must never
|
||||||
|
* be able to clobber a healthy 5000+ node snapshot, regardless of the
|
||||||
|
* upstream cause (genesis fallback, partial load, etc.). */
|
||||||
|
{
|
||||||
|
struct stat _st;
|
||||||
|
if (stat(p, &_st) == 0 && _st.st_size > 200000 &&
|
||||||
|
(uint64_t)b.len < (uint64_t)_st.st_size / 16) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"[engram_save] REFUSED sparse write: new %zu bytes vs existing "
|
||||||
|
"%lld bytes (< 1/16) — protecting snapshot %s\n",
|
||||||
|
b.len, (long long)_st.st_size, p);
|
||||||
|
free(b.buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Atomic write: tmp + fsync + rename ------------------------------
|
||||||
|
* Write to a sibling temp file, fsync it durable, then rename() over the
|
||||||
|
* target. rename() is atomic on POSIX, so a concurrent reader (a booting
|
||||||
|
* soul's engram_load) never observes a truncated or 0-byte snapshot —
|
||||||
|
* which was the root of the genesis/clobber loop. */
|
||||||
|
size_t _plen = strlen(p);
|
||||||
|
char* _tmp = (char*)malloc(_plen + 5);
|
||||||
|
if (!_tmp) { free(b.buf); return 0; }
|
||||||
|
memcpy(_tmp, p, _plen);
|
||||||
|
memcpy(_tmp + _plen, ".tmp", 5); /* includes NUL */
|
||||||
|
|
||||||
|
FILE* f = fopen(_tmp, "wb");
|
||||||
|
if (!f) { free(_tmp); free(b.buf); return 0; }
|
||||||
size_t w = fwrite(b.buf, 1, b.len, f);
|
size_t w = fwrite(b.buf, 1, b.len, f);
|
||||||
|
int wok = (w == b.len);
|
||||||
|
if (wok) { fflush(f); fsync(fileno(f)); }
|
||||||
fclose(f);
|
fclose(f);
|
||||||
int ok = (w == b.len);
|
|
||||||
free(b.buf);
|
free(b.buf);
|
||||||
return ok ? 1 : 0;
|
|
||||||
|
if (!wok) { unlink(_tmp); free(_tmp); return 0; }
|
||||||
|
if (rename(_tmp, p) != 0) { unlink(_tmp); free(_tmp); return 0; }
|
||||||
|
free(_tmp);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helper: extract a string field from a JSON object substring. */
|
/* Helper: extract a string field from a JSON object substring. */
|
||||||
|
|||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# build-soul-darwin.sh — replicate `elb` on macOS/arm64 with clang.
|
||||||
|
# Proven 2026-06-16: produces a Mach-O arm64 soul that boots and serves :7770.
|
||||||
|
# The official builder `elb` ships Linux-only (CI); this lets us build + test the
|
||||||
|
# darwin soul locally (e.g. to validate the atomic engram_save fix in isolation).
|
||||||
|
#
|
||||||
|
# Usage: scripts/build-soul-darwin.sh <path-to-neuron/dist> [output-binary]
|
||||||
|
set -e
|
||||||
|
DIST="${1:?usage: build-soul-darwin.sh <neuron/dist dir> [out]}"
|
||||||
|
OUT="${2:-./neuron}"
|
||||||
|
RT="$(cd "$(dirname "$0")/.." && pwd)/lang/el-compiler/runtime"
|
||||||
|
B="$(mktemp -d)"
|
||||||
|
# elc-generated dist modules use C89-style implicit cross-module declarations that
|
||||||
|
# Apple clang rejects as errors by default; resolve at link, so downgrade them.
|
||||||
|
CFLAGS="-Wno-implicit-function-declaration -Wno-implicit-int -Wno-int-conversion -I$B -I$DIST -I$RT"
|
||||||
|
cp "$RT/el_runtime.h" "$B/"
|
||||||
|
clang -c $CFLAGS "$RT/el_runtime.c" -o "$B/el_runtime.o"
|
||||||
|
for c in "$DIST"/*.c; do clang -c $CFLAGS "$c" -o "$B/$(basename "$c" .c).o"; done
|
||||||
|
# NOTE: link *.o once — do not also list el_runtime.o separately (duplicate symbols).
|
||||||
|
clang "$B"/*.o -o "$OUT" -lcurl -lm
|
||||||
|
echo "built $OUT"
|
||||||
Reference in New Issue
Block a user