The crash (SIGTRAP in engram_activate -> eg_vindex_sync -> vindex_insert ->
_realloc) had three read paths mutating five process-global statics.
engram_activate, eg_knn_for_node (whose own comment says "No writes.") and
engram_geo_reify_run_json all called eg_vindex_sync, which frees the index,
reallocs the seen-map and inserts — on a read.
Three moves, in decreasing order of how much they dissolve:
1. Misfiled scratch is not shared state. visited/visit_epoch/visited_cap
were never owned by the index; they are one traversal's local, hoisted
into struct VIndex as an allocation optimisation. They want neither a
lock nor a capability nor a pool — just to go back in the call frame.
Two concurrent READS stomped each other purely because of this.
2. const IS the capability. Once the scratch leaves the struct, search
reads and nothing else, so vindex_search takes a const VIndex*. That is
exactly what a capability-pointer ABI would have bought — a read path
physically cannot call vindex_insert, enforced by the compiler on every
future caller — for one qualifier instead of an ABI swept across
hundreds of builtins.
3. What survives is publication, not ownership. HNSW insert is NOT an
append: it rewires the neighbour links of already-existing elements and
reallocs elems[], so the store's append-only property does not transfer
to the index derived from it. eg_vindex_sync therefore splits into
eg_vindex_maintain (exclusive, sole mutator) and eg_vindex_view (shared,
returns const VIndex*). A read path may demand that a current snapshot
exist — a request to the owner, not a mutation by the reader.
Write-side owner: eg_vindex_note_embedded hooks the embedding-ASSIGNMENT
sites rather than the append sites, because a node with no embedding cannot
be in a vector index — embedding assignment is the event that owns index
membership. One O(log n) insert, no O(node_count) presence scan. This also
retires the "STALENESS (honest tradeoff)" note where a lazily-embedded
older node stayed invisible to route_nearest/autoconnect until a full
rebuild (the embed-gap #20 shape).
Evidence. The existing harness conflated two hazards, which is why fixing
half of it read as failure. Split into four:
single (3000 vec, ASan+UBSan) clean -> clean
readers (4 readers, no writer, TSan) RACE -> clean
unsynchronized (writer+reader, bare) race -> race, expected forever
published (owner + 4 readers) n/a -> clean, 3000/3000 landed
RESULT: PASS. recall@10 = 0.9365 at ef_search=128 (gate >= 0.90);
determinism byte-identical across two independent builds.
The unsynchronized half is now permanently expected to race, deliberately:
it is the executable proof that the boundary must live above the data
structure, not inside it.
fb32d15's guard is KEPT, correcting this design's own section 5. Measured,
it guards TWO structures and only one was converted here: g->nodes/g->edges
are realloc'd in place (el_runtime.c:7618,7629) and engram_activate_inner's
embed-backfill writes n->emb through exactly such a borrowed pointer.
Deleting the guard reintroduces a measured 11171->9579 edge loss. Its
comment is narrowed to the RAM graph and the deletion precondition named.
That corrects the ordering claim too: the residual is not one ABI that
dissolves everything at once, it is a PROPERTY applied per structure.
Residues evaporate in the order the property is applied, and a residue
whose structure has not been converted must be left standing.
Adds an O(1) "seen" bitmap so lazily-embedded older nodes get picked up
incrementally instead of only on a full rebuild (embed-gap #20).
Replaces engram_activate's O(N*D) cosine prescan with a lazy-memoized
cosine cache (eg_cosq_at), proven bit-identical to the old path.
Extracts a clean vindex_harvest_from_store primitive (read-only vector
harvest, careful malloc/ownership/error-path handling) reused by both
index-build and the new vindex_bench.c — a read-only proof harness
comparing brute-force vs HNSW recall/latency on both the real store and
synthetic data.
.nsbx-env intentionally excluded — local sandbox config (ports, paths,
dev-only placeholder key), not checked in.
Will waived diff review -> build it for real. Add engram_boundary_beat() to the
runtime (afferent counter++ + engram_chrono_tick + engram_strengthen(self-anchor)
+ dharma_emit) and two act-stats counters (aff_boundary_ops, dharma_emits).
codegen cg_fn injects ONE engram_boundary_beat(op) at the entry of every
@manager/@accessor fn (fn_has_decorator, so it fires under @route @manager too) —
a decorated op self-reports with ZERO hand-written instrumentation. Rebuilt elc
self-host + the cognition engram in the worktree; ran it as the clone daemon on
:8900. Proof (/api/boundary-proof, @manager, empty body, 5x): aff_boundary_ops
0->5, dharma_emits 0->5, self activation_count 1510->1513, chrono stamp advanced.
Brought in feat/cognitive-architecture engram runtime+server for the build.
strengthen = activation bump (not content/edge write) -> identity protection
intact. Live :8742 untouched; no push, no cutover.