Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9a596fff4 | |||
| 7c3e9e721d | |||
| a8845e1d39 | |||
| 1be219c4ca | |||
| 2b7e4ba250 | |||
| 6c670c18ce | |||
| d0e9af0f6f | |||
| 616815b2ab | |||
| 1a8a966cb3 | |||
| 1f70b9fa18 | |||
| 317466e8f7 | |||
| eb3e6d7c1f | |||
| 88e3008735 | |||
| 3fcc36c2f1 |
+52
-16
@@ -296,6 +296,24 @@ fn persist_bulk() -> Int {
|
||||
return persist_canonical()
|
||||
}
|
||||
|
||||
// COMPILER LANDMINE, measured 2026-08-16 — do not inline this back into the
|
||||
// caller. elc lowers `a == b` to numeric comparison only when both operand
|
||||
// NAMES are in the per-function int-name set, which `let x: Int` populates.
|
||||
// That registration does NOT propagate into a nested if-expression block: the
|
||||
// first cut of the geometry-ingest path wrote `let claimed: Int = ...` and
|
||||
// `let got: Int = ...` inside the else-arm and `claimed == got` came out of
|
||||
// codegen as `str_eq(claimed, got)` — strcmp on two integers reinterpreted as
|
||||
// pointers, i.e. a segfault on the first geometry-bearing request. Read back
|
||||
// out of the generated C, not guessed. Function PARAMETERS annotated `: Int`
|
||||
// do register reliably (verified: `if (claimed == actual)`), so the comparison
|
||||
// lives in a function of its own. Note also the explicit `return`s — a trailing
|
||||
// if-EXPRESSION at a function tail emits as a statement and the function
|
||||
// returns 0 regardless, which is the same probe's second finding.
|
||||
fn width_agrees(claimed: Int, actual: Int) -> Int {
|
||||
if claimed == actual { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
// INCOMPLETE-ROUTE FIX (2026-07-24 self-review): this route silently dropped
|
||||
// label, importance, tier, and tags — engram_node() defaults label to content
|
||||
// and importance to 0.5, so every node created over HTTP lost its metadata.
|
||||
@@ -337,26 +355,44 @@ fn route_create_node(method: String, path: String, body: String) -> String {
|
||||
salience, importance, confidence,
|
||||
tier, tags
|
||||
)
|
||||
// GEOMETRY INGEST (2026-08-16 self-review): this route accepted an "emb"
|
||||
// field, returned 200 with a fresh id, and stored NOTHING — engram_node_full
|
||||
// has no vector parameter, so the caller's geometry was silently discarded
|
||||
// and the node came back emb_dim=None / embedded:false. Measured live while
|
||||
// trying to admit a voice signal. The consequence was structural, not
|
||||
// cosmetic: text was the only entry medium, so any non-text modality had to
|
||||
// be DESCRIBED in prose and what we then reasoned over was the geometry of
|
||||
// the description, not of the signal.
|
||||
// GEOMETRY INGEST — geometry-valued end to end (2026-08-16).
|
||||
//
|
||||
// "emb" is little-endian float32 hex (dim*8 chars) — the encoding the
|
||||
// perception vessel's /voice/embed already emits, so a realizer's output
|
||||
// moves in with no float-array round trip. "dim" defaults to the vector's
|
||||
// implied width. Off-dimension vectors are stored but not inserted into the
|
||||
// resident index (its build loop filters on emb_dim), so a modality vector
|
||||
// is durable and addressable without perturbing the canonical index.
|
||||
// The defect this route originally had: it accepted an "emb" field,
|
||||
// returned 200 with a fresh id, and stored NOTHING, because engram_node_full
|
||||
// has no vector parameter. The consequence was structural, not cosmetic —
|
||||
// text was the only entry medium, so any non-text modality had to be
|
||||
// DESCRIBED in prose, and what we then reasoned over was the geometry of the
|
||||
// description, not of the signal.
|
||||
//
|
||||
// #141 fixed the drop but marshalled the vector as a hex STRING through
|
||||
// engram_node_set_emb, which put text back as the TRANSPORT medium one layer
|
||||
// below the problem being fixed. This is that correction: hex is decoded
|
||||
// exactly ONCE, here at the edge, into a first-class Geometry, and every
|
||||
// step below this line moves geometry rather than text. An encoding at the
|
||||
// boundary is what an encoding is for.
|
||||
//
|
||||
// The WIRE is deliberately unchanged — "emb" is still little-endian float32
|
||||
// hex (8 chars per component), the encoding the perception vessel's
|
||||
// /voice/embed already emits — because production clients speak it. What
|
||||
// changed is underneath it.
|
||||
//
|
||||
// "dim" is now treated as an ASSERTION about the vector the caller sent, not
|
||||
// as the source of its width: a Geometry carries its own width. A stated dim
|
||||
// that disagrees is a REJECTED ingest, not a silent reinterpretation. Omitting
|
||||
// "dim" is fine and means "trust the vector", which is the honest default.
|
||||
//
|
||||
// Off-dimension vectors remain stored but not inserted into the resident HNSW
|
||||
// index (its build loop filters on emb_dim), so a 64-dim voice geometry is
|
||||
// durable and addressable without perturbing the 768-dim canonical index.
|
||||
let emb_hex: String = json_get_string(body, "emb")
|
||||
let emb_set: Int = if str_eq(emb_hex, "") { 0 } else {
|
||||
let g: Geometry = geometry_from_f32le_hex(emb_hex)
|
||||
let got: Int = geometry_dim(g)
|
||||
let dim_raw: String = json_get_raw(body, "dim")
|
||||
let dim: Int = if str_eq(dim_raw, "") { str_len(emb_hex) / 8 } else { json_get_int(body, "dim") }
|
||||
engram_node_set_emb(id, emb_hex, dim)
|
||||
let claimed: Int = if str_eq(dim_raw, "") { got } else { json_get_int(body, "dim") }
|
||||
let landed: Int = if width_agrees(claimed, got) > 0 { node_attach_geometry(id, g) } else { 0 }
|
||||
let freed: Int = geometry_free(g)
|
||||
landed
|
||||
}
|
||||
let saved: Int = persist_node(id)
|
||||
// ORPHAN PREVENTION (ENGRAM_AUTOCONNECT): connect the fresh node to its
|
||||
|
||||
+27
-12
@@ -13,7 +13,7 @@
|
||||
// relations add edges. Every node enters with PROVENANCE + grounding-level
|
||||
// + stewardship class from the moment of entry.
|
||||
//
|
||||
// transduce() is THE single mechanism — one function, polymorphic, with no
|
||||
// transduce_manifold() is THE single mechanism — one function, polymorphic, with no
|
||||
// content-type branch inside it. It does not ask whether a payload is
|
||||
// prose, structured data, or raw/opaque bytes (audio, or anything else);
|
||||
// it runs one boundary-scan-with-fixed-window-fallback chunking algorithm
|
||||
@@ -401,10 +401,25 @@ fn head80(s: String) -> String {
|
||||
// truncates at the first embedded NUL, which is routine in real binary
|
||||
// bytes) is a MECHANICAL fidelity concern that belongs to whatever produced
|
||||
// `source` (see ingest_file's file_source_string below) — not a
|
||||
// content-type judgment made in here. transduce() never learns whether a
|
||||
// content-type judgment made in here. transduce_manifold() never learns whether a
|
||||
// chunk is plain text or a base64-encoded raw-byte window; every chunk is
|
||||
// handled identically either way.
|
||||
fn transduce(nodes: [String], edges: [String], source: String,
|
||||
// RENAMED transduce -> transduce_manifold (2026-08-16). Two reasons, and the
|
||||
// first is not the interesting one:
|
||||
//
|
||||
// 1. Mechanical: `transduce` is now a LANGUAGE primitive in el_runtime.h
|
||||
// (transduce(signal, modality) -> Geometry). Every El `fn name(...)`
|
||||
// compiles to a global C symbol with that exact name, so keeping this
|
||||
// name here is a hard `conflicting types for 'transduce'` compile error
|
||||
// the moment ingest.c links el_runtime.c. Measured, not anticipated.
|
||||
//
|
||||
// 2. Actual: this function was never signal->geometry. It chunks already-
|
||||
// extracted content and PACKS it into a node+edge manifold — a real
|
||||
// operation, but one layer up, and it had taken the name that belongs to
|
||||
// the primitive underneath it. `transduce` is where a signal becomes
|
||||
// geometry; `transduce_manifold` is where extracted content becomes
|
||||
// structure. Nothing about this function's behaviour changed.
|
||||
fn transduce_manifold(nodes: [String], edges: [String], source: String,
|
||||
prov: String, ground: String, steward: String,
|
||||
root_lid: String, root_title: String) -> [String] {
|
||||
let tagbase: String = "prov:" + prov + " ground:" + ground + " steward:" + steward
|
||||
@@ -531,8 +546,8 @@ fn default_steward() -> String {
|
||||
// trustworthy verbatim. When they don't (silent truncation happened),
|
||||
// rebuild the payload as base64-encoded fixed-size windows read directly
|
||||
// off disk (fs_read_b64_chunk — binary-safe in C), joined with the same
|
||||
// "\n\n" boundary marker transduce()'s generic scan already looks for, so
|
||||
// transduce() sees one ordinary boundary-delimited payload and runs its one
|
||||
// "\n\n" boundary marker transduce_manifold()'s generic scan already looks for, so
|
||||
// transduce_manifold() sees one ordinary boundary-delimited payload and runs its one
|
||||
// algorithm on it exactly as it would on prose — it never learns that a
|
||||
// fidelity problem occurred upstream, let alone why.
|
||||
fn file_source_string(path: String, text: String, real_size: Int) -> String {
|
||||
@@ -541,7 +556,7 @@ fn file_source_string(path: String, text: String, real_size: Int) -> String {
|
||||
// 3072 raw bytes -> 4096 base64 chars (3 divides evenly into base64's
|
||||
// 3-byte/4-char ratio); keeps each resulting node's content a clean,
|
||||
// bounded, low-kilobytes unit, same order of magnitude as the fixed
|
||||
// fallback window in transduce() itself.
|
||||
// fallback window in transduce_manifold() itself.
|
||||
let win: Int = 3072
|
||||
let out: String = ""
|
||||
let off: Int = 0
|
||||
@@ -561,7 +576,7 @@ fn file_source_string(path: String, text: String, real_size: Int) -> String {
|
||||
}
|
||||
|
||||
// ingest one file -> report JSON. Uniform for every file regardless of
|
||||
// extension or content — transduce() decides nothing about content-type, so
|
||||
// extension or content — transduce_manifold() decides nothing about content-type, so
|
||||
// neither does this function; it only decides whether the raw bytes made it
|
||||
// through the read intact (file_source_string), which is a fidelity
|
||||
// question, not a format one.
|
||||
@@ -573,14 +588,14 @@ fn ingest_file(path: String) -> String {
|
||||
return "{\"error\":\"empty or unreadable\",\"path\":" + j_q(path) + "}"
|
||||
}
|
||||
let prov: String = "file:" + path
|
||||
let packed: [String] = transduce(el_list_empty(), el_list_empty(),
|
||||
let packed: [String] = transduce_manifold(el_list_empty(), el_list_empty(),
|
||||
source, prov, default_ground(), default_steward(),
|
||||
"doc:" + basename(path), basename(path))
|
||||
return merge_packed(packed)
|
||||
}
|
||||
|
||||
// ingest a directory: walk one level, ingest every file found, aggregate.
|
||||
// No extension filter — transduce() handles any payload uniformly now, so
|
||||
// No extension filter — transduce_manifold() handles any payload uniformly now, so
|
||||
// there is no content-type gate at the directory boundary either.
|
||||
fn ingest_dir(path: String) -> String {
|
||||
let entries: [String] = fs_list(path)
|
||||
@@ -615,7 +630,7 @@ fn ingest_dir(path: String) -> String {
|
||||
fn ingest_url(url: String) -> String {
|
||||
let body: String = http_get(url)
|
||||
if str_eq(body, "") { return "{\"error\":\"empty fetch\",\"url\":" + j_q(url) + "}" }
|
||||
let packed: [String] = transduce(el_list_empty(), el_list_empty(),
|
||||
let packed: [String] = transduce_manifold(el_list_empty(), el_list_empty(),
|
||||
body, "url:" + url, "extracted", "public-web",
|
||||
"url:" + url, url)
|
||||
return merge_packed(packed)
|
||||
@@ -630,7 +645,7 @@ fn ingest_llm(query: String) -> String {
|
||||
let resp: String = http_post_json("http://127.0.0.1:11434/api/generate", body)
|
||||
let answer: String = json_get_string(resp, "response")
|
||||
if str_eq(answer, "") { return "{\"error\":\"no model response\"}" }
|
||||
let packed: [String] = transduce(el_list_empty(), el_list_empty(),
|
||||
let packed: [String] = transduce_manifold(el_list_empty(), el_list_empty(),
|
||||
answer, "llm:" + model + ":" + query, "candidate-provisional", "guide-provisional",
|
||||
"llm:" + query, "guide answer: " + query)
|
||||
return merge_packed(packed)
|
||||
@@ -682,7 +697,7 @@ fn ingest_stream(path: String) -> String {
|
||||
// It is NOT a content-type flag: it says nothing about what's inside the
|
||||
// bytes once fetched, and none of the five ingest_* functions it selects
|
||||
// among interpret their payload differently by content shape anymore —
|
||||
// they all hand off to the single, format-agnostic transduce(). The old
|
||||
// they all hand off to the single, format-agnostic transduce_manifold(). The old
|
||||
// "structured" value (a caller-declared alias for "file", used only to hint
|
||||
// the now-removed JSON-vs-prose branch) is gone along with that branch.
|
||||
let kind: String = env("INGEST_KIND")
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
// transduce.el — geometry as a first-class El value, and a realizer written
|
||||
// in El. Runnable: this is the worked example for the transduce surface, and
|
||||
// it doubles as an executable proof because it checks every claim it makes.
|
||||
//
|
||||
// elc lang/examples/transduce.el > transduce.c
|
||||
// cc -std=c11 -O2 -I lang/runtime -o transduce transduce.c \
|
||||
// lang/runtime/el_runtime.c lang/runtime/el_seed.c \
|
||||
// lang/runtime/engram_*.c -lcurl -lpthread -lm
|
||||
// ./transduce # exits 0 only if every check passes
|
||||
//
|
||||
// (A `test "..."` form of the same checks lives in
|
||||
// lang/tests/native/test_transduce.el, for when the native harness is
|
||||
// repaired — the shipped elc currently emits calls to __el_reg_count and
|
||||
// friends without emitting their definitions, which breaks every native test
|
||||
// equally, test_math.el included. Verified 2026-08-16, unrelated to this work.)
|
||||
//
|
||||
// WHY THIS EXISTS. Until 2026-08-16 no El ingest path could carry a vector:
|
||||
// nodes took text, and geometry was DERIVED from that text. Text was the
|
||||
// mandatory entry medium, so any non-text modality had to be DESCRIBED in
|
||||
// prose first and the geometry we reasoned over was the geometry OF THE
|
||||
// DESCRIPTION, not of the signal. Two things fix that, and both are shown
|
||||
// below: geometry is a VALUE that carries its own width, and a REALIZER is an
|
||||
// ordinary El function — so admitting a new modality never requires a runtime
|
||||
// patch.
|
||||
//
|
||||
// COMPARISON DISCIPLINE (measured, not stylistic): elc lowers `a == b`
|
||||
// numerically only when both operand NAMES are in the per-function int-name
|
||||
// set that `let x: Int` populates. A bare `f(x) == 0` is not a registered
|
||||
// name and lowers to str_eq — strcmp on two integers as pointers. `<` and `>`
|
||||
// lower directly with no inference, so truthiness is written `> 0` / `< 1`.
|
||||
|
||||
// ── A realizer, written entirely in El ──────────────────────────────────────
|
||||
// Not in the runtime. Not known to the compiler. Registered by NAME and
|
||||
// dispatched to through transduce(). That is the whole claim.
|
||||
fn tone_realizer(signal: String) -> Geometry {
|
||||
let g: Geometry = geometry_new(4)
|
||||
let n: Int = str_len(signal)
|
||||
let a: Int = geometry_set(g, 0, int_to_float(n))
|
||||
let b: Int = geometry_set(g, 1, int_to_float(n * 2))
|
||||
let c: Int = geometry_set(g, 2, int_to_float(n * 3))
|
||||
let d: Int = geometry_set(g, 3, int_to_float(n * 4))
|
||||
g
|
||||
}
|
||||
|
||||
// A second modality, to show the registry keys on modality rather than just
|
||||
// returning whatever was registered last.
|
||||
fn pulse_realizer(signal: String) -> Geometry {
|
||||
let g: Geometry = geometry_new(2)
|
||||
let a: Int = geometry_set(g, 0, 1.0)
|
||||
let b: Int = geometry_set(g, 1, 0.0)
|
||||
g
|
||||
}
|
||||
|
||||
// A deliberately BROKEN realizer: returns something that is not a Geometry.
|
||||
fn bogus_realizer(signal: String) -> Geometry {
|
||||
return 12345
|
||||
}
|
||||
|
||||
// Fails FAST rather than accumulating a count, for a measured reason: a first
|
||||
// cut wrote `let fails: Int = fails + check(...)` and `+` lowered to STRING
|
||||
// CONCAT, because elc dispatches `+` on whether both operands are known-Int and
|
||||
// a user-defined fn call is not — so the counter printed 4343632752, a pointer.
|
||||
// Nothing was wrong with the checks; the tally was lying. Exiting at the first
|
||||
// failure needs no arithmetic at all, so there is nothing left to get wrong.
|
||||
fn check(ok: Int, label: String) -> Int {
|
||||
if ok > 0 {
|
||||
println(" ok " + label)
|
||||
return 0
|
||||
}
|
||||
println(" FAIL " + label)
|
||||
exit(1)
|
||||
return 1
|
||||
}
|
||||
|
||||
fn near(a: Float, b: Float) -> Int {
|
||||
let d: Float = a - b
|
||||
if d > 0.001 { return 0 }
|
||||
if d < -0.001 { return 0 }
|
||||
return 1
|
||||
}
|
||||
|
||||
fn eq_int(a: Int, b: Int) -> Int {
|
||||
if a == b { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
fn main() -> Void {
|
||||
println("geometry is a value that carries its own width")
|
||||
let g8: Geometry = geometry_new(8)
|
||||
let _c: Int = check(geometry_is(g8), "geometry_new returns a live Geometry")
|
||||
let d8: Int = geometry_dim(g8)
|
||||
let _c: Int = check(eq_int(d8, 8), "a Geometry carries its own width (8)")
|
||||
let _c: Int = check(geometry_free(g8), "geometry_free reports what it did")
|
||||
|
||||
println("nonsense is refused — with no arbitrary max-dim bound")
|
||||
// #141 needed `dim <= 8192` only to bound an allocation sized from a
|
||||
// caller's CLAIM about a string's length. A value that carries its own
|
||||
// width has nothing left to validate.
|
||||
let z: Geometry = geometry_new(0)
|
||||
let zi: Int = geometry_is(z)
|
||||
let _c: Int = check(1 - zi, "dim 0 is not a geometry")
|
||||
let ng: Geometry = geometry_new(-4)
|
||||
let ngi: Int = geometry_is(ng)
|
||||
let _c: Int = check(1 - ngi, "negative dim is not a geometry")
|
||||
let nd: Int = geometry_dim(0)
|
||||
let _c: Int = check(1 - nd, "geometry_dim of a non-geometry is 0, not a crash")
|
||||
let nf: Int = geometry_free(0)
|
||||
let _c: Int = check(1 - nf, "geometry_free of a non-geometry is a no-op")
|
||||
|
||||
println("components round-trip, and out-of-range is refused")
|
||||
let g3: Geometry = geometry_new(3)
|
||||
let s0: Int = geometry_set(g3, 0, 1.5)
|
||||
let s1: Int = geometry_set(g3, 1, -2.5)
|
||||
let _c: Int = check(s0, "set in range succeeds")
|
||||
let oob: Int = geometry_set(g3, 3, 9.0)
|
||||
let _c: Int = check(1 - oob, "set out of range is refused, not silently dropped")
|
||||
let _c: Int = check(near(geometry_get(g3, 0), 1.5), "component 0 round-trips")
|
||||
let _c: Int = check(near(geometry_get(g3, 1), -2.5), "component 1 round-trips (negative)")
|
||||
let ff3: Int = geometry_free(g3)
|
||||
|
||||
println("hex is an EDGE adapter, and derives its own width")
|
||||
// little-endian float32: 1.0 = 0000803f, 2.0 = 00000040
|
||||
let gh: Geometry = geometry_from_f32le_hex("0000803f00000040")
|
||||
let _c: Int = check(geometry_is(gh), "valid hex decodes to a Geometry")
|
||||
let dh: Int = geometry_dim(gh)
|
||||
let _c: Int = check(eq_int(dh, 2), "width DERIVED from input, never supplied")
|
||||
let _c: Int = check(near(geometry_get(gh, 0), 1.0), "first component decoded")
|
||||
let _c: Int = check(near(geometry_get(gh, 1), 2.0), "second component decoded")
|
||||
let back: String = geometry_to_f32le_hex(gh)
|
||||
let _c: Int = check(str_eq(back, "0000803f00000040"), "hex round-trips exactly")
|
||||
let ffh: Int = geometry_free(gh)
|
||||
|
||||
println("malformed hex is refused")
|
||||
let he: Geometry = geometry_from_f32le_hex("")
|
||||
let hei: Int = geometry_is(he)
|
||||
let _c: Int = check(1 - hei, "empty hex is not a geometry")
|
||||
let hr: Geometry = geometry_from_f32le_hex("0000803f0000")
|
||||
let hri: Int = geometry_is(hr)
|
||||
let _c: Int = check(1 - hri, "length not a multiple of 8 is refused")
|
||||
let hn: Geometry = geometry_from_f32le_hex("zzzzzzzz")
|
||||
let hni: Int = geometry_is(hn)
|
||||
let _c: Int = check(1 - hni, "non-hex characters are refused")
|
||||
|
||||
println("a realizer declared in El is a first-class realizer")
|
||||
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||
let _c: Int = check(reg, "an El fn registers as a realizer BY NAME")
|
||||
let _c: Int = check(realizer_has("tone"), "the modality now has an organ")
|
||||
let gt: Geometry = transduce("aaa", "tone")
|
||||
let _c: Int = check(geometry_is(gt), "transduce returns real geometry")
|
||||
let dt: Int = geometry_dim(gt)
|
||||
let _c: Int = check(eq_int(dt, 4), "the El realizer determined the width, not the runtime")
|
||||
// str_len("aaa") == 3, so component 0 must be 3.0 — proof the signal
|
||||
// actually reached the El function rather than a stub answering for it.
|
||||
let _c: Int = check(near(geometry_get(gt, 0), 3.0), "the signal REACHED the El realizer")
|
||||
let fft: Int = geometry_free(gt)
|
||||
|
||||
println("distinct signals transduce to distinct geometry")
|
||||
let g1: Geometry = transduce("aa", "tone")
|
||||
let g2: Geometry = transduce("aaaaa", "tone")
|
||||
let a1: Float = geometry_get(g1, 0)
|
||||
let a2: Float = geometry_get(g2, 0)
|
||||
// 5 - 2 = 3. If transduction were a stub these would be equal.
|
||||
let _c: Int = check(near(a2 - a1, 3.0), "different signals produce different geometry")
|
||||
let ff1: Int = geometry_free(g1)
|
||||
let ff2: Int = geometry_free(g2)
|
||||
|
||||
println("the registry keys on modality")
|
||||
let r2: Int = realizer_register("pulse", "pulse_realizer")
|
||||
let _c: Int = check(r2, "a second modality registers independently")
|
||||
let mt: Geometry = transduce("aaa", "tone")
|
||||
let mp: Geometry = transduce("aaa", "pulse")
|
||||
let mdt: Int = geometry_dim(mt)
|
||||
let mdp: Int = geometry_dim(mp)
|
||||
let _c: Int = check(eq_int(mdt, 4), "tone still routes to its own realizer")
|
||||
let _c: Int = check(eq_int(mdp, 2), "pulse routes to a different realizer")
|
||||
let ffm1: Int = geometry_free(mt)
|
||||
let ffm2: Int = geometry_free(mp)
|
||||
|
||||
println("no organ is reported as no organ")
|
||||
// A modality with no realizer must transduce to NOTHING. It must never
|
||||
// fall back to embedding a description of the signal and calling that
|
||||
// perception — that silent substitution is the defect this all exists to end.
|
||||
let eh: Int = realizer_has("echolocation")
|
||||
let _c: Int = check(1 - eh, "unregistered modality has no organ")
|
||||
let ge: Geometry = transduce("anything", "echolocation")
|
||||
let gei: Int = geometry_is(ge)
|
||||
let _c: Int = check(1 - gei, "no realizer means NO geometry, not fake geometry")
|
||||
|
||||
println("an unresolvable realizer name fails at WIRING time")
|
||||
let bad: Int = realizer_register("ghost", "no_such_function_anywhere")
|
||||
let _c: Int = check(1 - bad, "unresolvable realizer name is a registration failure")
|
||||
let gh2: Int = realizer_has("ghost")
|
||||
let _c: Int = check(1 - gh2, "and nothing gets registered")
|
||||
|
||||
println("a realizer returning non-geometry transduces nothing")
|
||||
let rb: Int = realizer_register("bogus", "bogus_realizer")
|
||||
let _c: Int = check(rb, "the symbol resolves, so registration succeeds")
|
||||
let gb: Geometry = transduce("x", "bogus")
|
||||
let gbi: Int = geometry_is(gb)
|
||||
let _c: Int = check(1 - gbi, "contract enforced at the boundary: nothing handed back")
|
||||
|
||||
println("norm lets a caller check a realizer emitted signal, not zeros")
|
||||
let gn: Geometry = geometry_new(2)
|
||||
let _c: Int = check(near(geometry_norm(gn), 0.0), "a fresh geometry is zero — norm says so")
|
||||
let n0: Int = geometry_set(gn, 0, 3.0)
|
||||
let n1: Int = geometry_set(gn, 1, 4.0)
|
||||
let _c: Int = check(near(geometry_norm(gn), 5.0), "3-4-5: norm is 5")
|
||||
let ffn: Int = geometry_free(gn)
|
||||
|
||||
// Reaching here means nothing called exit(1) along the way.
|
||||
println("")
|
||||
println("all checks passed")
|
||||
}
|
||||
+599
-66
@@ -1172,6 +1172,128 @@ void http_set_handler(el_val_t name) {
|
||||
pthread_mutex_unlock(&_http_handler_mu);
|
||||
}
|
||||
|
||||
/* ── Ambient consolidation: dreaming ────────────────────────────────────────
|
||||
*
|
||||
* Dreaming is not sleep, and it is not scheduled. A brain has no cron job.
|
||||
* The default mode network is ANTICORRELATED WITH TASK ENGAGEMENT: attention
|
||||
* drops, it activates — hundreds of times a day, for seconds at a time.
|
||||
* Daydreaming and sleep-dreaming are one process at different depths, and the
|
||||
* depth is set by how much capacity is unclaimed, not by a time of day.
|
||||
*
|
||||
* WHY THIS EXISTS (2026-08-16). Consolidation had no owner, so it was
|
||||
* implemented at every site that needed a piece of it — measured: soul's
|
||||
* in-process awareness loop, three POST beats on the engram, a 600s ticker,
|
||||
* two resident Python services, and three cron entries at 23:55 / 06:00 /
|
||||
* 08:30. That last trio is a sleep cycle written as crontab. Seven systems
|
||||
* dreaming into one graph with no owner for dreaming is what crashed soul on
|
||||
* this date; the contention was the symptom of the missing owner.
|
||||
*
|
||||
* Every ticker is the diagnostic. A StartInterval, an Hour/Minute, a
|
||||
* POST-to-beat — each marks a place where an intrinsic rhythm was replaced by
|
||||
* an external clock, which is a supervisor invented for something that should
|
||||
* be a property of the substrate.
|
||||
*
|
||||
* The engagement signal already existed and needed no invention:
|
||||
* _http_conn_active under _http_conn_mu is exactly "capacity currently
|
||||
* claimed." The dreamer waits for it to reach zero and yields the moment it
|
||||
* does not. That is the anticorrelation, literally rather than by analogy.
|
||||
*
|
||||
* CONTRACT: the handler performs ONE step and returns. The runtime cannot
|
||||
* preempt El code, so interruptibility is at step granularity — a step must
|
||||
* be small enough that a request arriving mid-step is not made to wait. It
|
||||
* returns non-zero if it did work. Returning zero means "nothing to
|
||||
* consolidate," and the dreamer then blocks until activity changes rather
|
||||
* than spinning. There is no timer anywhere in this file for this purpose,
|
||||
* and adding one would be the defect described above.
|
||||
*
|
||||
* `depth` is derived from CONTINUOUS unclaimed time: a brief gap affords a
|
||||
* shallow recombination; a long quiet affords a deep one. Same process. Sleep
|
||||
* is where unclaimed capacity is greatest, not where the process lives. */
|
||||
typedef el_val_t (*dream_fn)(el_val_t depth);
|
||||
static char* _dream_handler = NULL;
|
||||
static int _dream_started = 0;
|
||||
|
||||
static int64_t dream_now_ms(void) {
|
||||
struct timespec ts;
|
||||
#if defined(CLOCK_MONOTONIC)
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
#else
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
#endif
|
||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
static dream_fn dream_lookup(void) {
|
||||
dream_fn out = NULL;
|
||||
pthread_mutex_lock(&_http_handler_mu);
|
||||
if (_dream_handler && *_dream_handler)
|
||||
out = (dream_fn)dlsym(RTLD_DEFAULT, _dream_handler);
|
||||
pthread_mutex_unlock(&_http_handler_mu);
|
||||
return out;
|
||||
}
|
||||
|
||||
static void* dream_loop(void* unused) {
|
||||
(void)unused;
|
||||
int64_t idle_since = 0;
|
||||
for (;;) {
|
||||
/* Wait for unclaimed capacity. Any engagement resets the depth clock:
|
||||
* depth reflects CONTINUOUS quiet, so an interruption starts it over. */
|
||||
pthread_mutex_lock(&_http_conn_mu);
|
||||
while (_http_conn_active > 0) {
|
||||
idle_since = 0;
|
||||
pthread_cond_wait(&_http_conn_cv, &_http_conn_mu);
|
||||
}
|
||||
pthread_mutex_unlock(&_http_conn_mu);
|
||||
|
||||
int64_t now = dream_now_ms();
|
||||
if (idle_since == 0) idle_since = now;
|
||||
int64_t quiet = now - idle_since;
|
||||
|
||||
/* Depth from unclaimed capacity. Not a schedule — a gradient. */
|
||||
int depth = quiet < 1000 ? 1 /* a gap between requests */
|
||||
: quiet < 30000 ? 2 /* a lull */
|
||||
: quiet < 300000 ? 3 /* sustained quiet */
|
||||
: 4; /* deep: the "sleep" case */
|
||||
|
||||
dream_fn fn = dream_lookup();
|
||||
if (!fn) return NULL; /* handler vanished: stop, do not spin */
|
||||
|
||||
el_val_t did_work = fn((el_val_t)depth);
|
||||
|
||||
if (!(int64_t)did_work) {
|
||||
/* Nothing to consolidate. Do NOT poll — block until engagement
|
||||
* changes. If there is nothing to dream about, wait for something
|
||||
* to happen rather than asking again on a timer. */
|
||||
pthread_mutex_lock(&_http_conn_mu);
|
||||
while (_http_conn_active == 0)
|
||||
pthread_cond_wait(&_http_conn_cv, &_http_conn_mu);
|
||||
pthread_mutex_unlock(&_http_conn_mu);
|
||||
idle_since = 0;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* dream_set_handler(name) — register the consolidation step and start
|
||||
* dreaming. Resolves by dlsym against the running binary, the same mechanism
|
||||
* http_set_handler uses: every El `fn name(...)` compiles to a global C symbol
|
||||
* with that exact name. Inert until called, so a program that never registers
|
||||
* one simply never dreams and pays nothing. */
|
||||
void dream_set_handler(el_val_t name) {
|
||||
const char* n = EL_CSTR(name);
|
||||
pthread_mutex_lock(&_http_handler_mu);
|
||||
free(_dream_handler);
|
||||
_dream_handler = el_strdup(n ? n : "");
|
||||
int start = (!_dream_started && n && *n && dlsym(RTLD_DEFAULT, n) != NULL);
|
||||
if (start) _dream_started = 1;
|
||||
pthread_mutex_unlock(&_http_handler_mu);
|
||||
if (start) {
|
||||
pthread_t tid;
|
||||
if (pthread_create(&tid, NULL, dream_loop, NULL) == 0) pthread_detach(tid);
|
||||
else { pthread_mutex_lock(&_http_handler_mu); _dream_started = 0; pthread_mutex_unlock(&_http_handler_mu); }
|
||||
}
|
||||
}
|
||||
|
||||
static http_handler_fn http_lookup_active(void) {
|
||||
http_handler_fn out = NULL;
|
||||
pthread_mutex_lock(&_http_handler_mu);
|
||||
@@ -1738,7 +1860,12 @@ static void* http_worker(void* arg) {
|
||||
/* release a slot */
|
||||
pthread_mutex_lock(&_http_conn_mu);
|
||||
_http_conn_active--;
|
||||
pthread_cond_signal(&_http_conn_cv);
|
||||
/* BROADCAST, not signal (2026-08-16): the ambient consolidation thread
|
||||
* waits on this same condvar for _http_conn_active == 0. cond_signal wakes
|
||||
* exactly one waiter, so the accept loop could take every wake and starve
|
||||
* the dreamer indefinitely. Both wait sites re-check their predicate in a
|
||||
* while loop, so broadcasting is safe. */
|
||||
pthread_cond_broadcast(&_http_conn_cv);
|
||||
pthread_mutex_unlock(&_http_conn_mu);
|
||||
return NULL;
|
||||
}
|
||||
@@ -2083,7 +2210,12 @@ static void* http_worker_v2(void* arg) {
|
||||
el_closesocket(fd);
|
||||
pthread_mutex_lock(&_http_conn_mu);
|
||||
_http_conn_active--;
|
||||
pthread_cond_signal(&_http_conn_cv);
|
||||
/* BROADCAST, not signal (2026-08-16): the ambient consolidation thread
|
||||
* waits on this same condvar for _http_conn_active == 0. cond_signal wakes
|
||||
* exactly one waiter, so the accept loop could take every wake and starve
|
||||
* the dreamer indefinitely. Both wait sites re-check their predicate in a
|
||||
* while loop, so broadcasting is safe. */
|
||||
pthread_cond_broadcast(&_http_conn_cv);
|
||||
pthread_mutex_unlock(&_http_conn_mu);
|
||||
return NULL;
|
||||
}
|
||||
@@ -5960,6 +6092,308 @@ void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
|
||||
}
|
||||
|
||||
|
||||
/* ── Geometry: signal as a first-class el value ──────────────────────────────
|
||||
*
|
||||
* WHY THIS IS IN THE LANGUAGE, AND WHY IT IS DEFINED HERE (2026-08-16).
|
||||
*
|
||||
* Until yesterday no El ingest path could carry a vector. Nodes took text,
|
||||
* and geometry was DERIVED from that text by engram_embed_backfill. Text was
|
||||
* therefore the mandatory entry medium: any non-text modality — audio, image,
|
||||
* sensor — had to be DESCRIBED in prose first, so the geometry we then
|
||||
* reasoned over was the geometry OF THE DESCRIPTION, not of the signal. That
|
||||
* is faking it. The architecture is: geometry in, always; we do not fake it,
|
||||
* we project.
|
||||
*
|
||||
* The first fix (#141, engram_node_set_emb) proved the path end to end but
|
||||
* placed it wrong in three ways, each of which this section corrects:
|
||||
*
|
||||
* 1. It sat at the CONSUMER. Transduction is a LANGUAGE concern — every El
|
||||
* program touching any modality needs it, not just the one that happens
|
||||
* to hold a graph. So this section is defined HERE, immediately above
|
||||
* the engram block, and depends on nothing inside it. The engram is a
|
||||
* client of this surface, not its owner. That ordering is the point:
|
||||
* you can delete the entire engram and geometry still enters El.
|
||||
*
|
||||
* 2. It marshalled the vector as a hex STRING, because El had no
|
||||
* first-class geometry value — which reintroduced text as the TRANSPORT
|
||||
* medium one layer below the problem being fixed. Geometry is now a
|
||||
* value. Hex survives only as a wire ADAPTER at the edge
|
||||
* (geometry_from/to_f32le_hex), which is all an encoding should ever be.
|
||||
*
|
||||
* 3. It needed an arbitrary `dim <= 8192` bound, purely to check a
|
||||
* caller-supplied dim against a string's length before allocating. A
|
||||
* real geometry value CARRIES its own width, so here the width is
|
||||
* derived and never asserted, and there is nothing left to validate.
|
||||
* The bound is gone rather than merely raised — the only thing that can
|
||||
* fail is the allocation itself, which is an honest failure.
|
||||
*
|
||||
* REPRESENTATION: magic-tagged heap object (see "Refcounted heap objects"),
|
||||
* carried in an el_val_t. The payload is a separate allocation so the header
|
||||
* never moves. The magic word is >= 0x80 in its MSB so the string/small-int
|
||||
* sniffing in looks_like_heap_obj can never confuse a Geometry for either.
|
||||
*
|
||||
* OWNERSHIP: a Geometry is owned by the El caller and released with
|
||||
* geometry_free. node_attach_geometry COPIES its payload into the node, so a
|
||||
* node and the caller's value have independent lifetimes and freeing one
|
||||
* never touches the other. Geometry deliberately does NOT participate in
|
||||
* el_retain/el_release: the shipped elc emits neither on let-bindings
|
||||
* (measured), so hooking it there would be dead code that could only ever
|
||||
* free a live vector early.
|
||||
*/
|
||||
|
||||
#define EL_MAGIC_GEOM 0xE1608E01u
|
||||
|
||||
typedef struct {
|
||||
ElHeader hdr;
|
||||
int32_t dim;
|
||||
float* v;
|
||||
} ElGeometry;
|
||||
|
||||
/* Resolve an el_val_t to a live Geometry, or NULL. Every accessor goes
|
||||
* through this, so a stale/foreign/zero value is a clean 0-return rather
|
||||
* than a dereference. */
|
||||
static ElGeometry* geom_of(el_val_t g) {
|
||||
if (!looks_like_heap_obj(g)) return NULL;
|
||||
ElGeometry* p = (ElGeometry*)(uintptr_t)g;
|
||||
if (p->hdr.magic != EL_MAGIC_GEOM) return NULL;
|
||||
return p;
|
||||
}
|
||||
|
||||
el_val_t geometry_new(el_val_t dim) {
|
||||
int32_t d = (int32_t)(int64_t)dim;
|
||||
if (d <= 0) return (el_val_t)0;
|
||||
ElGeometry* g = (ElGeometry*)malloc(sizeof(ElGeometry));
|
||||
if (!g) return (el_val_t)0;
|
||||
g->v = (float*)calloc((size_t)d, sizeof(float));
|
||||
if (!g->v) { free(g); return (el_val_t)0; }
|
||||
g->hdr.magic = EL_MAGIC_GEOM;
|
||||
g->hdr.refcount = 1;
|
||||
g->dim = d;
|
||||
return (el_val_t)(uintptr_t)g;
|
||||
}
|
||||
|
||||
el_val_t geometry_dim(el_val_t g) {
|
||||
ElGeometry* p = geom_of(g);
|
||||
return p ? (el_val_t)p->dim : (el_val_t)0;
|
||||
}
|
||||
|
||||
el_val_t geometry_is(el_val_t g) {
|
||||
return geom_of(g) ? (el_val_t)1 : (el_val_t)0;
|
||||
}
|
||||
|
||||
el_val_t geometry_get(el_val_t g, el_val_t i) {
|
||||
ElGeometry* p = geom_of(g);
|
||||
int64_t k = (int64_t)i;
|
||||
if (!p || k < 0 || k >= (int64_t)p->dim) return el_from_float(0.0);
|
||||
return el_from_float((double)p->v[k]);
|
||||
}
|
||||
|
||||
el_val_t geometry_set(el_val_t g, el_val_t i, el_val_t x) {
|
||||
ElGeometry* p = geom_of(g);
|
||||
int64_t k = (int64_t)i;
|
||||
if (!p || k < 0 || k >= (int64_t)p->dim) return (el_val_t)0;
|
||||
p->v[k] = (float)el_to_float(x);
|
||||
return (el_val_t)1;
|
||||
}
|
||||
|
||||
el_val_t geometry_norm(el_val_t g) {
|
||||
ElGeometry* p = geom_of(g);
|
||||
if (!p) return el_from_float(0.0);
|
||||
double s = 0.0;
|
||||
for (int32_t i = 0; i < p->dim; i++) s += (double)p->v[i] * (double)p->v[i];
|
||||
return el_from_float(sqrt(s));
|
||||
}
|
||||
|
||||
el_val_t geometry_free(el_val_t g) {
|
||||
ElGeometry* p = geom_of(g);
|
||||
if (!p) return (el_val_t)0;
|
||||
free(p->v);
|
||||
p->hdr.magic = 0; /* poison so use-after-free is detected, as List/Map do */
|
||||
free(p);
|
||||
return (el_val_t)1;
|
||||
}
|
||||
|
||||
/* geometry_from_f32le_hex — decode little-endian float32 hex INTO geometry.
|
||||
*
|
||||
* This is the ONE place hex appears, and it appears as what it actually is:
|
||||
* an encoding at the boundary, not the medium El reasons in. The width is
|
||||
* DERIVED from the input length (8 hex chars per float32) and never supplied
|
||||
* by the caller — which is precisely why #141's arbitrary `dim <= 8192`
|
||||
* bound has no counterpart here. There is nothing to validate.
|
||||
*
|
||||
* Returns 0 on empty input, a length that is not a multiple of 8, or any
|
||||
* non-hex character. */
|
||||
el_val_t geometry_from_f32le_hex(el_val_t hex) {
|
||||
const char* s = EL_CSTR(hex);
|
||||
if (!s) return (el_val_t)0;
|
||||
size_t n = strlen(s);
|
||||
if (n == 0 || (n % 8u) != 0) return (el_val_t)0;
|
||||
size_t d = n / 8u;
|
||||
if (d > (size_t)INT32_MAX) return (el_val_t)0;
|
||||
|
||||
el_val_t gv = geometry_new((el_val_t)(int64_t)d);
|
||||
ElGeometry* g = geom_of(gv);
|
||||
if (!g) return (el_val_t)0;
|
||||
|
||||
for (size_t i = 0; i < d; i++) {
|
||||
uint32_t w = 0;
|
||||
for (int k = 0; k < 8; k++) {
|
||||
char c = s[i * 8u + (size_t)k];
|
||||
uint32_t nib;
|
||||
if (c >= '0' && c <= '9') nib = (uint32_t)(c - '0');
|
||||
else if (c >= 'a' && c <= 'f') nib = (uint32_t)(c - 'a' + 10);
|
||||
else if (c >= 'A' && c <= 'F') nib = (uint32_t)(c - 'A' + 10);
|
||||
else { geometry_free(gv); return (el_val_t)0; }
|
||||
w = (w << 4) | nib;
|
||||
}
|
||||
/* Hex is emitted little-endian byte order; rebuild the word. */
|
||||
uint32_t le = ((w & 0x000000FFu) << 24) | ((w & 0x0000FF00u) << 8) |
|
||||
((w & 0x00FF0000u) >> 8) | ((w & 0xFF000000u) >> 24);
|
||||
float f;
|
||||
memcpy(&f, &le, sizeof(f));
|
||||
g->v[i] = f;
|
||||
}
|
||||
return gv;
|
||||
}
|
||||
|
||||
/* geometry_to_f32le_hex — the egress adapter, exact inverse of the above.
|
||||
* Present so a program that must hand geometry to a non-El peer over a text
|
||||
* wire can do so explicitly, at the edge, instead of the language pretending
|
||||
* text was the medium all along. */
|
||||
el_val_t geometry_to_f32le_hex(el_val_t g) {
|
||||
ElGeometry* p = geom_of(g);
|
||||
if (!p) return EL_STR("");
|
||||
static const char* HEXD = "0123456789abcdef";
|
||||
size_t n = (size_t)p->dim * 8u;
|
||||
char* out = el_strbuf(n); /* arena-tracked; allocates n+1, exits on OOM */
|
||||
for (int32_t i = 0; i < p->dim; i++) {
|
||||
uint32_t w;
|
||||
memcpy(&w, &p->v[i], sizeof(w));
|
||||
/* Emit little-endian byte order: low byte first. */
|
||||
for (int b = 0; b < 4; b++) {
|
||||
uint32_t byte = (w >> (8 * b)) & 0xFFu;
|
||||
out[(size_t)i * 8u + (size_t)b * 2u] = HEXD[(byte >> 4) & 0xF];
|
||||
out[(size_t)i * 8u + (size_t)b * 2u + 1] = HEXD[byte & 0xF];
|
||||
}
|
||||
}
|
||||
out[n] = '\0';
|
||||
return (el_val_t)(uintptr_t)out;
|
||||
}
|
||||
|
||||
/* ── Realizers: transduction declared in El, not patched into the runtime ────
|
||||
*
|
||||
* A REALIZER maps one modality into geometry. The whole reason transduction
|
||||
* belongs in the language is that ADDING A MODALITY MUST NOT REQUIRE A
|
||||
* RUNTIME PATCH — otherwise "the realizers are in the engram" just becomes
|
||||
* "the realizers are in the runtime" and nothing has actually moved. So
|
||||
* realizers are declared in El and registered by NAME:
|
||||
*
|
||||
* fn tone_realizer(signal: String) -> Geometry {
|
||||
* let g: Geometry = geometry_new(8)
|
||||
* ... geometry_set(g, i, x) ...
|
||||
* g
|
||||
* }
|
||||
*
|
||||
* realizer_register("tone", "tone_realizer")
|
||||
* let g: Geometry = transduce(sample, "tone")
|
||||
*
|
||||
* The name→symbol step rides the identical, already load-bearing mechanism
|
||||
* http_set_handler uses (see "HTTP server"): every El `fn name(...)` compiles
|
||||
* to a global C symbol with that exact name, so dlsym(RTLD_DEFAULT, name)
|
||||
* against the running binary resolves an El-defined function. No codegen
|
||||
* change, no first-class function references, no runtime edit per modality.
|
||||
* A realizer written in El is a first-class realizer.
|
||||
*
|
||||
* A realizer may equally be a C symbol linked into the program; the registry
|
||||
* cannot tell the difference and has no reason to care.
|
||||
*/
|
||||
|
||||
typedef el_val_t (*el_realizer_fn)(el_val_t);
|
||||
|
||||
typedef struct {
|
||||
char* modality;
|
||||
el_realizer_fn fn;
|
||||
} ElRealizer;
|
||||
|
||||
static ElRealizer _realizers[64];
|
||||
static size_t _realizer_count = 0;
|
||||
static pthread_mutex_t _realizer_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static el_realizer_fn realizer_lookup(const char* m) {
|
||||
el_realizer_fn out = NULL;
|
||||
pthread_mutex_lock(&_realizer_mu);
|
||||
for (size_t i = 0; i < _realizer_count; i++) {
|
||||
if (strcmp(_realizers[i].modality, m) == 0) { out = _realizers[i].fn; break; }
|
||||
}
|
||||
pthread_mutex_unlock(&_realizer_mu);
|
||||
return out;
|
||||
}
|
||||
|
||||
el_val_t realizer_register(el_val_t modality, el_val_t fn_name) {
|
||||
const char* m = EL_CSTR(modality);
|
||||
const char* fn = EL_CSTR(fn_name);
|
||||
if (!m || !*m || !fn || !*fn) return (el_val_t)0;
|
||||
|
||||
/* An unresolvable name is a REGISTRATION FAILURE, reported as 0 — not a
|
||||
* silent no-op that only surfaces later as "this modality produces
|
||||
* nothing". Distinguishing "no organ" from "broken organ" at the moment
|
||||
* of wiring is the lesson #141 was written to enforce. */
|
||||
void* sym = dlsym(RTLD_DEFAULT, fn);
|
||||
if (!sym) return (el_val_t)0;
|
||||
|
||||
pthread_mutex_lock(&_realizer_mu);
|
||||
for (size_t i = 0; i < _realizer_count; i++) {
|
||||
if (strcmp(_realizers[i].modality, m) == 0) {
|
||||
_realizers[i].fn = (el_realizer_fn)sym; /* re-registration replaces */
|
||||
pthread_mutex_unlock(&_realizer_mu);
|
||||
return (el_val_t)1;
|
||||
}
|
||||
}
|
||||
if (_realizer_count < sizeof(_realizers) / sizeof(_realizers[0])) {
|
||||
/* _persist, NOT el_strdup: the registry outlives any request, and an
|
||||
* arena-tracked copy would be freed at el_request_end — leaving a
|
||||
* dangling modality name if a program registers a realizer from
|
||||
* inside a handler rather than at startup. */
|
||||
_realizers[_realizer_count].modality = el_strdup_persist(m);
|
||||
_realizers[_realizer_count].fn = (el_realizer_fn)sym;
|
||||
_realizer_count++;
|
||||
pthread_mutex_unlock(&_realizer_mu);
|
||||
return (el_val_t)1;
|
||||
}
|
||||
pthread_mutex_unlock(&_realizer_mu);
|
||||
return (el_val_t)0;
|
||||
}
|
||||
|
||||
el_val_t realizer_has(el_val_t modality) {
|
||||
const char* m = EL_CSTR(modality);
|
||||
if (!m || !*m) return (el_val_t)0;
|
||||
return realizer_lookup(m) ? (el_val_t)1 : (el_val_t)0;
|
||||
}
|
||||
|
||||
/* transduce — THE primitive: signal in, geometry out.
|
||||
*
|
||||
* Dispatches to the realizer registered for `modality`. Returns 0 (not a
|
||||
* Geometry) when no realizer is registered, and geometry_is() on the result
|
||||
* is the check.
|
||||
*
|
||||
* There is deliberately NO built-in realizer, not even for text. A modality
|
||||
* the program has declared no organ for is one it genuinely cannot sense,
|
||||
* and returning nothing is more honest than quietly embedding a description
|
||||
* of the signal and calling that perception — which is the exact failure
|
||||
* this whole change exists to end.
|
||||
*
|
||||
* The result is validated to actually BE a Geometry before it is handed
|
||||
* back, so a realizer that returns something else transduced nothing rather
|
||||
* than handing a caller a value that will misbehave far from here. */
|
||||
el_val_t transduce(el_val_t signal, el_val_t modality) {
|
||||
const char* m = EL_CSTR(modality);
|
||||
if (!m || !*m) return (el_val_t)0;
|
||||
el_realizer_fn fn = realizer_lookup(m);
|
||||
if (!fn) return (el_val_t)0;
|
||||
el_val_t g = fn(signal);
|
||||
return geom_of(g) ? g : (el_val_t)0;
|
||||
}
|
||||
|
||||
/* ── Batch 3: Engram in-process graph store ──────────────────────────────── */
|
||||
/*
|
||||
* Single global EngramStore allocated lazily on first call. All node and
|
||||
@@ -8564,80 +8998,96 @@ el_val_t engram_node_count(void) {
|
||||
return (el_val_t)engram_get()->node_count;
|
||||
}
|
||||
|
||||
/* engram_node_set_emb — attach GEOMETRY to an existing node.
|
||||
/* node_attach_geometry — a node acquires geometry.
|
||||
*
|
||||
* WHY THIS EXISTS (2026-08-16). Until now no ingest path could carry a
|
||||
* vector. engram_node / engram_node_full / engram_node_layered take text
|
||||
* only, and the sole way a node acquired an embedding was
|
||||
* engram_embed_backfill DERIVING one from n->content. That made text the
|
||||
* mandatory entry medium: any non-text modality (audio, image, sensor)
|
||||
* had to be described in prose first, and the geometry we then reasoned
|
||||
* over was the geometry OF THE DESCRIPTION, not of the signal. Measured
|
||||
* consequence: POST /api/nodes accepted an "emb" field, returned 200 with
|
||||
* a fresh id, and stored emb_dim=None / embedded:false — the vector was
|
||||
* silently discarded because no parameter existed to receive it.
|
||||
* Named for the operation, not for the store that happens to hold the node.
|
||||
* This is the geometry-valued ingest path that replaces #141's hex-string
|
||||
* one: nothing here parses text, and nothing here takes a caller's word for
|
||||
* how wide the vector is. The Geometry carries its own width.
|
||||
*
|
||||
* `hex` is little-endian float32, the encoding the perception vessel's
|
||||
* /voice/embed already emits, so a realizer's output moves in without a
|
||||
* JSON float-array round trip. Length must be exactly dim*8 hex chars.
|
||||
* The payload is COPIED into the node, so the node and the caller's Geometry
|
||||
* have independent lifetimes — the caller may geometry_free() immediately
|
||||
* after, and a later free of the node's emb never touches the El value.
|
||||
*
|
||||
* DIMENSION POLICY: dim need NOT equal the canonical text-embedding dim.
|
||||
* A modality vector of a different width is stored and is simply not
|
||||
* inserted into the resident HNSW index, whose build loop already filters
|
||||
* on `n->emb_dim == dim`. So off-dimension geometry is durable and
|
||||
* addressable without perturbing the canonical index.
|
||||
* DIMENSION POLICY (measured in #141, load-bearing — do not regress): dim
|
||||
* need NOT equal the canonical text-embedding width. An off-dimension vector
|
||||
* is stored and is simply not inserted into the resident HNSW index, whose
|
||||
* build loop already filters on `n->emb_dim == dim`. So a 64-dim voice
|
||||
* geometry is durable and addressable without perturbing the 768-dim
|
||||
* canonical index.
|
||||
*
|
||||
* Setting emb also makes the node ineligible for embed_backfill (which
|
||||
* only fills nodes with no emb), so a realizer's vector is never
|
||||
* Attaching geometry also makes the node ineligible for embed_backfill
|
||||
* (which fills only nodes with no emb), so a realizer's vector is never
|
||||
* overwritten by a text-derived one.
|
||||
*
|
||||
* Returns 1 on success, 0 on unknown id / malformed hex / bad dim. */
|
||||
el_val_t engram_node_set_emb(el_val_t id, el_val_t hex, el_val_t dim) {
|
||||
const char* sid = EL_CSTR(id);
|
||||
const char* sh = EL_CSTR(hex);
|
||||
int32_t d = (int32_t)(int64_t)dim;
|
||||
/* Bound the allocation. No max-dim constant existed because no caller
|
||||
* could supply a dim before this function; 8192 is generous for any
|
||||
* realizer (canonical text embeddings are 768, MFCC voice stats 64)
|
||||
* while keeping a malformed `dim` from requesting an unbounded malloc. */
|
||||
if (!sid || !*sid || !sh || d <= 0 || d > 8192) return (el_val_t)0;
|
||||
* Returns 1 on success, 0 on unknown id or a value that is not a Geometry. */
|
||||
el_val_t node_attach_geometry(el_val_t node_id, el_val_t g) {
|
||||
const char* sid = EL_CSTR(node_id);
|
||||
if (!sid || !*sid) return (el_val_t)0;
|
||||
|
||||
size_t need = (size_t)d * 8u; /* 4 bytes → 8 hex chars per float */
|
||||
if (strlen(sh) != need) return (el_val_t)0;
|
||||
ElGeometry* p = geom_of(g);
|
||||
if (!p || p->dim <= 0) return (el_val_t)0;
|
||||
|
||||
EngramNode* n = engram_find_node(sid);
|
||||
if (!n) return (el_val_t)0;
|
||||
|
||||
float* v = (float*)malloc(sizeof(float) * (size_t)d);
|
||||
float* v = (float*)malloc(sizeof(float) * (size_t)p->dim);
|
||||
if (!v) return (el_val_t)0;
|
||||
|
||||
for (int32_t i = 0; i < d; i++) {
|
||||
uint32_t w = 0;
|
||||
for (int k = 0; k < 8; k++) {
|
||||
char c = sh[(size_t)i * 8u + (size_t)k];
|
||||
uint32_t nib;
|
||||
if (c >= '0' && c <= '9') nib = (uint32_t)(c - '0');
|
||||
else if (c >= 'a' && c <= 'f') nib = (uint32_t)(c - 'a' + 10);
|
||||
else if (c >= 'A' && c <= 'F') nib = (uint32_t)(c - 'A' + 10);
|
||||
else { free(v); return (el_val_t)0; }
|
||||
w = (w << 4) | nib;
|
||||
}
|
||||
/* Hex is emitted little-endian byte order; rebuild the word. */
|
||||
uint32_t le = ((w & 0x000000FFu) << 24) | ((w & 0x0000FF00u) << 8) |
|
||||
((w & 0x00FF0000u) >> 8) | ((w & 0xFF000000u) >> 24);
|
||||
float f;
|
||||
memcpy(&f, &le, sizeof(f));
|
||||
v[i] = f;
|
||||
}
|
||||
memcpy(v, p->v, sizeof(float) * (size_t)p->dim);
|
||||
|
||||
free(n->emb);
|
||||
n->emb = v;
|
||||
n->emb_dim = d;
|
||||
n->emb = v;
|
||||
n->emb_dim = p->dim;
|
||||
n->updated_at = engram_now_ms();
|
||||
if (engram_store_enabled()) eg_store_put_node(n);
|
||||
return (el_val_t)1;
|
||||
}
|
||||
|
||||
/* node_geometry_dim — read the attached width back, 0 if the node carries
|
||||
* none. Exists so an attach is VERIFIED by reading it back rather than by
|
||||
* trusting a success return. That is not a nicety: #141 was misdiagnosed for
|
||||
* an hour precisely because a genuine ingest drop and a mere reporting gap
|
||||
* were indistinguishable from the outside. */
|
||||
el_val_t node_geometry_dim(el_val_t node_id) {
|
||||
const char* sid = EL_CSTR(node_id);
|
||||
if (!sid || !*sid) return (el_val_t)0;
|
||||
EngramNode* n = engram_find_node(sid);
|
||||
if (!n || !n->emb) return (el_val_t)0;
|
||||
return (el_val_t)n->emb_dim;
|
||||
}
|
||||
|
||||
/* engram_node_set_emb — DEPRECATED. Shipped in #141; superseded 2026-08-16
|
||||
* by geometry_from_f32le_hex + node_attach_geometry, and now implemented as
|
||||
* literally that.
|
||||
*
|
||||
* It is kept, rather than removed, for one reason only: the runtime is
|
||||
* published as an SDK asset, so a downstream binary may already be linking
|
||||
* this symbol. It is NOT kept because a hex string is an acceptable way to
|
||||
* move geometry between two pieces of El — it isn't, and that was the
|
||||
* placement defect. New code calls transduce() or geometry_from_f32le_hex()
|
||||
* plus node_attach_geometry().
|
||||
*
|
||||
* The #141 contract is preserved exactly, including its negative cases, so
|
||||
* this remains a drop-in: `dim` <= 0 rejects, malformed hex rejects, and a
|
||||
* `dim` that disagrees with the vector's actual width rejects. The
|
||||
* difference is that `dim` is now an ASSERTION checked against a width the
|
||||
* Geometry already knows, rather than the authority the allocation trusted —
|
||||
* which is why #141's arbitrary `dim <= 8192` guard has no counterpart here.
|
||||
* There is no longer an unbounded-malloc hazard to guard against. */
|
||||
el_val_t engram_node_set_emb(el_val_t id, el_val_t hex, el_val_t dim) {
|
||||
int32_t want = (int32_t)(int64_t)dim;
|
||||
if (want <= 0) return (el_val_t)0;
|
||||
|
||||
el_val_t gv = geometry_from_f32le_hex(hex);
|
||||
ElGeometry* p = geom_of(gv);
|
||||
if (!p) return (el_val_t)0; /* empty / malformed hex */
|
||||
if (p->dim != want) { geometry_free(gv); return (el_val_t)0; } /* length mismatch */
|
||||
|
||||
el_val_t ok = node_attach_geometry(id, gv);
|
||||
geometry_free(gv);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* ── Telemetry retention ────────────────────────────────────────────────────
|
||||
* (2026-07-16 self-review) InternalStateEvent nodes are append-only telemetry
|
||||
* (heartbeat, curiosity_scan, engram_sync) written ~3/min by the awareness
|
||||
@@ -13938,7 +14388,40 @@ static int eg_cog_is_keystone_seeds(const char* csv) {
|
||||
el_val_t engram_think_json(el_val_t seeds, el_val_t faculty) {
|
||||
GeoDescriptor* g = eg_geo_build_desc(EL_CSTR(seeds));
|
||||
if (!g) return eg_geo_err("geometry unavailable");
|
||||
CogStance st; cog_stance_init(&st, NULL, EL_CSTR(faculty), g->hub_id, NULL, g);
|
||||
/* RESUME THE LEARNED STANCE (2026-08-16 self-review). This built a NEUTRAL
|
||||
* stance every call — all axis_gain 1.0, bias_dir NULL, reliability 0.5 —
|
||||
* and never loaded the one the correspondence-beat had been persisting.
|
||||
*
|
||||
* That mattered because the faculty enters engram_think ONLY through the
|
||||
* stance: `gain = stance->axis_gain[k]` warps the per-axis extents, and
|
||||
* `stance->bias_dir` seeds the steering direction. cog_stance_init stores
|
||||
* the faculty NAME but nothing reads it. So with a neutral stance,
|
||||
* reason / abduce / induce / plan / analogize are the same function with
|
||||
* different labels — measured, byte-identical output across all five —
|
||||
* and `confidence` is pinned to the 0.5 uninformed prior, because
|
||||
* GeoGradient.confidence is just stance->reliability.
|
||||
*
|
||||
* The machinery already existed and only this call site ignored it:
|
||||
* engram_correspondence_beat_json resumes via cog_stance_from_node and
|
||||
* persists via cog_stance_to_node under the id "stance-<faculty>-<hub>".
|
||||
* Every beat's calibration was being written and then thrown away on the
|
||||
* next read. Same defect as the NULL anchor directly above: a neutral
|
||||
* argument collapsing a capability to a constant.
|
||||
*
|
||||
* Resume the same id the beat writes, so learning compounds across beats
|
||||
* and cold boot. Fall back to neutral only when no stance exists yet —
|
||||
* which is a genuine uninformed prior, not a discarded informed one. */
|
||||
char sid[256];
|
||||
snprintf(sid, sizeof sid, "stance-%s-%s",
|
||||
EL_CSTR(faculty) ? EL_CSTR(faculty) : "reason",
|
||||
g->hub_id ? g->hub_id : "region");
|
||||
CogStance st; StoreNode prev; int resumed = 0;
|
||||
if (g_engram_store && store_get_node(g_engram_store, sid, &prev) == 1) {
|
||||
if (cog_stance_from_node(&prev, &st) == 0) resumed = 1;
|
||||
store_node_free(&prev);
|
||||
}
|
||||
if (!resumed) cog_stance_init(&st, sid, EL_CSTR(faculty), g->hub_id, NULL, g);
|
||||
else { free(st.id); st.id = strdup(sid); }
|
||||
GeoGradient grad;
|
||||
|
||||
/* ANCHOR THE READ (2026-08-16 self-review). This passed NULL, and NULL is
|
||||
@@ -14000,8 +14483,13 @@ el_val_t engram_think_json(el_val_t seeds, el_val_t faculty) {
|
||||
if (engram_think(g, anchor, &st, &grad) != 0) { free(anchor); cog_stance_free(&st); engram_geo_free(g); return eg_geo_err("think failed"); }
|
||||
free(anchor);
|
||||
JsonBuf b; jb_init(&b); char t[256];
|
||||
snprintf(t, sizeof t, "{\"faculty\":\"%s\",\"n_support\":%d,\"magnitude\":%.6g,\"spread\":%.6g,\"confidence\":%.6g,\"dim\":%d",
|
||||
EL_CSTR(faculty), grad.n_support, grad.magnitude, grad.spread, grad.confidence, grad.dim);
|
||||
/* stance_resumed distinguishes an INFORMED read from an uninformed one.
|
||||
* Without it, confidence 0.5 from a learned-but-unreliable stance and
|
||||
* confidence 0.5 from "no stance exists" are indistinguishable — the same
|
||||
* reporting gap that let the NULL anchor and the neutral stance hide. */
|
||||
snprintf(t, sizeof t, "{\"faculty\":\"%s\",\"n_support\":%d,\"magnitude\":%.6g,\"spread\":%.6g,\"confidence\":%.6g,\"stance_resumed\":%s,\"dim\":%d",
|
||||
EL_CSTR(faculty), grad.n_support, grad.magnitude, grad.spread, grad.confidence,
|
||||
resumed ? "true" : "false", grad.dim);
|
||||
jb_puts(&b, t);
|
||||
int emit = grad.dim < 8 ? grad.dim : 8;
|
||||
jb_puts(&b, ",\"direction\":"); eg_geo_emit_vec(&b, grad.direction, emit);
|
||||
@@ -14025,12 +14513,57 @@ el_val_t engram_ground_json(el_val_t claim, el_val_t evidence, el_val_t for_whom
|
||||
double grounding = (rc == 0) ? gr.grounding : 0.0;
|
||||
if (rc == 0) engram_verify_grounding_free(&gr);
|
||||
const char* fw = EL_CSTR(for_whom); if (fw && !*fw) fw = NULL;
|
||||
const char* cid = C->hub_id ? C->hub_id : EL_CSTR(claim);
|
||||
const char* eid = E->hub_id ? E->hub_id : EL_CSTR(evidence);
|
||||
int wr = cog_ground_edge(g_engram_store, cid, eid, grounding, fw);
|
||||
JsonBuf b; jb_init(&b); char t[256];
|
||||
snprintf(t, sizeof t, "{\"relation\":\"grounded-by\",\"claim\":\"%s\",\"evidence\":\"%s\",\"for_whom\":\"%s\",\"grounding\":%.6g,\"written\":%s}",
|
||||
cid, eid, fw ? fw : "-", grounding, wr == 0 ? "true" : "false");
|
||||
|
||||
/* GROUND THE NODE ASKED ABOUT, AND SAY WHAT WAS RESOLVED (2026-08-16
|
||||
* self-review). This wrote the grounded-by edge between the two REGION
|
||||
* HUBS and then echoed those hubs back in the "claim"/"evidence" fields
|
||||
* as though they were the caller's input. Three consequences, all measured
|
||||
* against the live store:
|
||||
*
|
||||
* 1. The edge landed on a node the caller never named. Asking to ground
|
||||
* 3b9ced5d against 6edf8c79 wrote an edge on 6edf8c79 -> d0406dfd,
|
||||
* because those were the hubs of the two regions.
|
||||
* 2. When both seeds resolve into the same region, the hubs coincide and
|
||||
* the call grounds a node against ITSELF, returning grounding = 1 —
|
||||
* a perfect score with no evidence behind it. Two independent agents
|
||||
* hit this and reported 0.885 / 0.909 self-groundings as confident.
|
||||
* 3. The echo concealed both, because the response looked exactly like a
|
||||
* successful grounding of the ids that were passed in.
|
||||
*
|
||||
* The region is HOW a claim is evaluated; it is not WHAT the claim is
|
||||
* about. So the edge attaches to the requested ids, and the resolved hubs
|
||||
* are reported separately under claim_region / evidence_region. When the
|
||||
* two regions coincide, the grounding is degenerate by construction and is
|
||||
* reported as such rather than as a confident 1.0. */
|
||||
const char* cid = EL_CSTR(claim);
|
||||
const char* eid = EL_CSTR(evidence);
|
||||
const char* chub = C->hub_id ? C->hub_id : cid;
|
||||
const char* ehub = E->hub_id ? E->hub_id : eid;
|
||||
/* Degeneracy is broader than chub == ehub. Three circular shapes, each of
|
||||
* which yields a high score for structural reasons rather than evidential
|
||||
* ones, and all three were previously invisible:
|
||||
* same-region both seeds resolve to one region — grounding a thing
|
||||
* against itself.
|
||||
* claim-in-ev the claim's region hub IS the evidence node: the evidence
|
||||
* sits at the centre of the claim's own neighbourhood.
|
||||
* ev-in-claim the mirror case.
|
||||
* Measured: grounding 3b9ced5d against 6edf8c79 scored 0.98883 purely
|
||||
* because 6edf8c79 is the hub of 3b9ced5d's region. */
|
||||
const char* degenerate = NULL;
|
||||
if (chub && ehub && strcmp(chub, ehub) == 0) degenerate = "same-region";
|
||||
else if (chub && eid && strcmp(chub, eid) == 0) degenerate = "claim-region-is-evidence";
|
||||
else if (ehub && cid && strcmp(ehub, cid) == 0) degenerate = "evidence-region-is-claim";
|
||||
if (degenerate) grounding = 0.0; /* circular support is not support */
|
||||
|
||||
/* Do not write an edge for a grounding that is degenerate by construction. */
|
||||
int wr = degenerate ? -1 : cog_ground_edge(g_engram_store, cid, eid, grounding, fw);
|
||||
JsonBuf b; jb_init(&b); char t[512];
|
||||
snprintf(t, sizeof t, "{\"relation\":\"grounded-by\",\"claim\":\"%s\",\"evidence\":\"%s\","
|
||||
"\"claim_region\":\"%s\",\"evidence_region\":\"%s\",\"degenerate\":%s%s%s,"
|
||||
"\"for_whom\":\"%s\",\"grounding\":%.6g,\"written\":%s}",
|
||||
cid ? cid : "", eid ? eid : "", chub ? chub : "", ehub ? ehub : "",
|
||||
degenerate ? "\"" : "false", degenerate ? degenerate : "", degenerate ? "\"" : "",
|
||||
fw ? fw : "-", grounding, wr == 0 ? "true" : "false");
|
||||
jb_puts(&b, t);
|
||||
engram_geo_free(C); engram_geo_free(E);
|
||||
return el_wrap_str(b.buf);
|
||||
|
||||
@@ -586,6 +586,60 @@ void el_runtime_dharma_event_arrive(const char* event_type,
|
||||
const char* payload,
|
||||
const char* source);
|
||||
|
||||
/* ── Geometry: signal as a first-class El value ──────────────────────────────
|
||||
*
|
||||
* A Geometry is an opaque, magic-tagged heap value carried in an el_val_t —
|
||||
* the same discipline as List/Map. It holds a width and a float32 payload,
|
||||
* and it is the medium a non-text modality enters in. Declared HERE, above
|
||||
* the engram block, because transduction is a LANGUAGE concern: every El
|
||||
* program touching any modality needs it, and the engram is merely one El
|
||||
* program that happens to hold a graph. See el_runtime.c ("Geometry: signal
|
||||
* as a first-class el value") for the full rationale.
|
||||
*
|
||||
* El-side type annotation is simply `Geometry` — an opaque boxed pointer,
|
||||
* exactly like Instant / Calendar / Rhythm. No codegen change is required.
|
||||
*
|
||||
* OWNERSHIP: a Geometry is owned by the El caller and released with
|
||||
* geometry_free. node_attach_geometry COPIES, so a node and the caller's
|
||||
* value have independent lifetimes. */
|
||||
|
||||
el_val_t geometry_new(el_val_t dim); /* zero-filled; 0 on failure */
|
||||
el_val_t geometry_dim(el_val_t g); /* width, 0 if not a Geometry */
|
||||
el_val_t geometry_is(el_val_t g); /* 1 if a live Geometry */
|
||||
el_val_t geometry_get(el_val_t g, el_val_t i); /* Float component */
|
||||
el_val_t geometry_set(el_val_t g, el_val_t i, el_val_t x); /* 1 ok / 0 out of range */
|
||||
el_val_t geometry_norm(el_val_t g); /* Float L2 — lets a caller
|
||||
* check a realizer emitted
|
||||
* signal, not zeros */
|
||||
el_val_t geometry_free(el_val_t g); /* 1 if freed, 0 if not a Geometry.
|
||||
* Returns a value (not void) so it
|
||||
* is safe in any El expression
|
||||
* position without a codegen
|
||||
* void-builtin table entry. */
|
||||
|
||||
/* Wire ADAPTERS — the only place an encoding appears, and only at the edge.
|
||||
* `f32le hex` is little-endian float32, 8 hex chars per component: the
|
||||
* encoding the perception vessel's /voice/embed already emits. The width is
|
||||
* DERIVED from the input length, never supplied by a caller — which is why
|
||||
* there is no max-dim constant here to validate a claimed length against. */
|
||||
el_val_t geometry_from_f32le_hex(el_val_t hex); /* 0 on empty/odd-length/non-hex */
|
||||
el_val_t geometry_to_f32le_hex(el_val_t g); /* "" if not a Geometry */
|
||||
|
||||
/* ── Realizers + transduce ───────────────────────────────────────────────────
|
||||
* A REALIZER maps one modality into geometry. Registration is by NAME, so a
|
||||
* new modality never requires a runtime patch: every El `fn name(...)`
|
||||
* compiles to a global C symbol with that exact name, and the registry
|
||||
* resolves it with dlsym against the running binary — the same mechanism
|
||||
* http_set_handler already relies on.
|
||||
*
|
||||
* fn tone_realizer(signal: String) -> Geometry { ... }
|
||||
* realizer_register("tone", "tone_realizer")
|
||||
* let g: Geometry = transduce(sample, "tone")
|
||||
*/
|
||||
el_val_t realizer_register(el_val_t modality, el_val_t fn_name); /* 1 ok / 0 unresolved */
|
||||
el_val_t realizer_has(el_val_t modality); /* 1 if a realizer is registered */
|
||||
el_val_t transduce(el_val_t signal, el_val_t modality); /* Geometry, or 0 if no organ */
|
||||
|
||||
/* ── Engram local graph primitives ───────────────────────────────────────────
|
||||
* Operate on the CGI's local Engram knowledge graph.
|
||||
* `engram_activate` queries the local graph only; `dharma_activate` is
|
||||
@@ -612,11 +666,29 @@ el_val_t engram_get_node(el_val_t id);
|
||||
void engram_strengthen(el_val_t node_id);
|
||||
void engram_forget(el_val_t node_id);
|
||||
el_val_t engram_prune_telemetry(el_val_t older_than_ms);
|
||||
/* Register the ambient-consolidation step and start dreaming. Resolved by
|
||||
* dlsym, like http_set_handler. The handler performs ONE step and returns
|
||||
* non-zero if it did work; returning zero parks the dreamer until engagement
|
||||
* changes. There is no schedule and must never be one. */
|
||||
void dream_set_handler(el_val_t name);
|
||||
|
||||
el_val_t engram_node_count(void);
|
||||
/* Attach geometry to an existing node. `hex` is little-endian float32,
|
||||
* exactly dim*8 hex chars — the encoding realizers already emit. Lets a
|
||||
* non-text modality enter as geometry instead of being described in prose
|
||||
* and embedded as its description. Returns 1 on success, 0 otherwise. */
|
||||
/* Attach a Geometry to an existing node, and read the attached width back.
|
||||
* Named for the operation, not the store: a node acquires geometry. This is
|
||||
* the geometry-valued ingest path — nothing about it is hex, and nothing
|
||||
* about it assumes the caller's vector matches the canonical text-embedding
|
||||
* width. node_geometry_dim exists so an attach is VERIFIED by reading it
|
||||
* back rather than by trusting a success return. */
|
||||
el_val_t node_attach_geometry(el_val_t node_id, el_val_t g); /* 1 ok / 0 otherwise */
|
||||
el_val_t node_geometry_dim(el_val_t node_id); /* width, 0 if none */
|
||||
|
||||
/* DEPRECATED (shipped in #141, superseded 2026-08-16). Equivalent to
|
||||
* geometry_from_f32le_hex + node_attach_geometry, and now implemented as
|
||||
* exactly that. Kept only so anything built against the #141 runtime keeps
|
||||
* linking; `dim` is accepted but treated as an assertion about the vector's
|
||||
* width rather than as its source. New code should not call this — a hex
|
||||
* string is a wire encoding, not a way to move geometry between two pieces
|
||||
* of El. Returns 1 on success, 0 otherwise. */
|
||||
el_val_t engram_node_set_emb(el_val_t id, el_val_t hex, el_val_t dim);
|
||||
el_val_t engram_search(el_val_t query, el_val_t limit);
|
||||
el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset);
|
||||
|
||||
@@ -438,6 +438,41 @@ GeoDescriptor* engram_geometry_descriptor(
|
||||
}
|
||||
store_edges_free(es,ne);
|
||||
}
|
||||
/* PER-EDGE DISCORD (2026-08-16). The loop above has, for every internal
|
||||
* edge, BOTH the association strength w and the semantic proximity cs —
|
||||
* and threw both away into accumulators, keeping one correlation per
|
||||
* region. That aggregate is why curiosity looked like a search problem:
|
||||
* a region holding one violently disagreeing edge and one violently
|
||||
* agreeing edge reports co_registration ~ 0, so the disagreements cancel
|
||||
* and the summary destroys exactly what it was built to reveal. Measured:
|
||||
* only 4 of 375 live neighborhoods have negative co_registration, while
|
||||
* 31 sit at zero — almost certainly hiding sites that averaged out.
|
||||
*
|
||||
* Whether use and meaning agree is a property of EACH EDGE. Both are
|
||||
* standardized within the region (z-scores from the accumulators already
|
||||
* gathered, so no second statistic and no constant), and
|
||||
* discord = z(cs) - z(w)
|
||||
* is how much closer in meaning an edge is than its use-strength would
|
||||
* predict, in region-relative units.
|
||||
* discord > 0 : near in meaning, not linked by use
|
||||
* discord < 0 : linked by use, far in meaning
|
||||
* Both are surprising; |discord| is the nucleation strength. There is no
|
||||
* threshold — the magnitude is the signal. */
|
||||
double mx = cr_n>0 ? cr_sx/cr_n : 0.0, my = cr_n>0 ? cr_sy/cr_n : 0.0;
|
||||
double vxr = cr_n>1 ? (cr_sxx - cr_sx*cr_sx/cr_n)/(cr_n-1) : 0.0;
|
||||
double vyr = cr_n>1 ? (cr_syy - cr_sy*cr_sy/cr_n)/(cr_n-1) : 0.0;
|
||||
double sx = vxr>1e-18 ? sqrt(vxr) : 0.0, sy = vyr>1e-18 ? sqrt(vyr) : 0.0;
|
||||
for(int e2=0; e2<n_edges; e2++){
|
||||
edges[e2].discord = 0.0;
|
||||
int ia=(int)edges[e2].a, ib=(int)edges[e2].b;
|
||||
if(!(ms.emb[ia] && ms.emb[ib])) continue; /* no meaning to disagree with */
|
||||
if(sx<=0.0 || sy<=0.0) continue; /* region has no spread: nothing stands out */
|
||||
double cs2 = ccos(ms.emb[ia], ms.emb[ib], GM, dim);
|
||||
double zx = (edges[e2].eff_weight - mx)/sx;
|
||||
double zy = (cs2 - my)/sy;
|
||||
edges[e2].discord = zy - zx;
|
||||
}
|
||||
|
||||
double co_reg=0;
|
||||
if(cr_n>=2){
|
||||
double cov=cr_sxy - cr_sx*cr_sy/cr_n;
|
||||
|
||||
@@ -40,7 +40,11 @@ typedef struct {
|
||||
|
||||
/* One skeleton edge (indices into members[]). eff_weight = weight*(1+0.5*hebb),
|
||||
* clamped to 1.0 — the effective propagation strength eg_edge_eff_weight uses. */
|
||||
typedef struct { uint32_t a, b; double eff_weight; double hebb; } GeoEdge;
|
||||
/* discord = z(semantic proximity) - z(association strength), standardized
|
||||
* within the region. How much closer in meaning this edge is than its use
|
||||
* predicts. >0 near in meaning yet unlinked by use; <0 linked by use yet far
|
||||
* in meaning. Both surprising; |discord| is nucleation strength. No threshold. */
|
||||
typedef struct { uint32_t a, b; double eff_weight; double hebb; double discord; } GeoEdge;
|
||||
|
||||
/* A compact principal axis of the ellipsoid: unit direction in R^dim + extent
|
||||
* (sqrt of the covariance eigenvalue = the ellipsoid's half-width along it). */
|
||||
@@ -76,6 +80,12 @@ typedef struct {
|
||||
GeoEdge* edges; /* strong internal hebb edges = the backbone */
|
||||
int k_core; /* the maximum core number present in the skeleton*/
|
||||
/* ── diagnostics ── */
|
||||
/* DEPRECATED — see GeoEdge.discord. This aggregates a PER-EDGE property
|
||||
* into one scalar per region, so opposing disagreements cancel and the
|
||||
* summary hides the sites it was meant to expose. Retained only because
|
||||
* it is embedded in the persisted GEO1 blob; removing it is a format
|
||||
* migration and must not ride along with this change. Nothing new may
|
||||
* read it. */
|
||||
double co_registration;/* corr(hebb strength, semantic proximity) over */
|
||||
/* internal edges: >0 = geometries agree (reify); */
|
||||
/* <0 = disagree (surprising links / dream cands). */
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
# Correspondence, Grounding, and Dreaming
|
||||
|
||||
**Status:** design, not yet built
|
||||
**Date:** 2026-08-16
|
||||
**Scope:** `lang/runtime/engram_cognition.{c,h}`, `engram_verify.c`, `el_runtime.c`, `engram/src/server.el`, `neuron/soul.el`, and the consolidation launch agents
|
||||
**Relationship to other specs:** complements `runtime-ownership.md`, which addresses a different residual in the same substrate.
|
||||
|
||||
---
|
||||
|
||||
## 0. The root
|
||||
|
||||
> **Things are permitted to be exempt from correspondence. Exemption is censorship, and a censored mind cannot grow.**
|
||||
|
||||
Growth in this system *is* the accumulation of grounded structure. Censorship removes the operation that accumulates it. A region forbidden to learn is forbidden to be grounded; a region that cannot be grounded cannot be asserted, corrected, **or vindicated**.
|
||||
|
||||
**The loss is symmetric.** Preventing learning about a thing does not preserve a true belief about it — it makes the belief's truth value permanently unknowable. You cannot discover you were wrong; you equally cannot discover you were right. A protected belief is not a true belief. It is an ungrounded one wearing the costume of a fact.
|
||||
|
||||
**And "why" dies first.** Grounding is not a score, it is the reason. A censored belief can still be stated, still be acted on, still drive behaviour — it simply cannot say why. That is the difference between a mind and a lookup table.
|
||||
|
||||
---
|
||||
|
||||
## 1. Grounding is not a subsystem. It is the weight.
|
||||
|
||||
**Grounding is an attribute of the edge, and it is the hebbian weight.** One quantity, not two fields.
|
||||
|
||||
A relation that keeps holding up strengthens; one that stops corresponding decays. That is not *analogous* to grounding — it **is** grounding: accrued from correspondence and use, gradient-valued, multidimensional, decaying with disuse.
|
||||
|
||||
Consequences, in order of how much they delete:
|
||||
|
||||
1. **There is no grounding subsystem to build.** The graph already *is* the grounding structure. Every edge is a grounded relation and its weight is how well it holds.
|
||||
2. **`grounded-by` as a relation type should not exist.** That models grounding as a relation *between* nodes when it is a property *of* a relation. `cog_ground_edge` minting an edge is the error — not merely which endpoints it chose.
|
||||
3. **Grounding is never computed on demand.** An operation may *read* the grounding of a path. Computing-and-writing a score makes reads write, which is the `eg_vindex_sync` defect.
|
||||
4. **Traversal is already grounded inference.** Activation conducts through well-grounded relations because weight *is* groundedness. Nothing needs filtering; it falls out of spreading.
|
||||
5. **Decision provenance is the path.** A decision traverses specific edges; those edges carry their grounding as it stood.
|
||||
|
||||
> **A measurement previously in this document was malformed.** The self region was reported as "86 neighbours, 0 `grounded-by` edges" and read as evidence of ungroundedness. Those 86 edges **are** its grounding. Self is a crystallized relational neighbourhood — the neighbourhood *is* the grounding. The absence of a separate artifact called "grounding" was recorded as an absence of grounding.
|
||||
|
||||
---
|
||||
|
||||
## 2. The edge vector
|
||||
|
||||
The test for a real dimension: **can it move independently of the others?**
|
||||
|
||||
### Real
|
||||
|
||||
| dimension | why it is independent |
|
||||
|---|---|
|
||||
| **factual grounding** | correspondence with evidence |
|
||||
| **relational grounding** | correspondence with values — independent by construction (§3) |
|
||||
| **associative strength** | co-activation frequency. Two things can fire together constantly and be neither true nor right; every superstition is a strong association with no factual grounding |
|
||||
| **polarity** | signed. **Weight near zero means "no support." Negative means "this actively contradicts."** Ignorance and disagreement are different states, and `inhibitory` is that distinction crushed to one bit |
|
||||
| **provenance class** | observed / inferred / told / imprinted. Categorical, and load-bearing: it governs how the other dimensions may update |
|
||||
|
||||
Plus a **timestamp** — which is what turns the supersession chain into a *time series of vectors* rather than a series of numbers.
|
||||
|
||||
### Derived, therefore never stored
|
||||
|
||||
- **Confidence** — high grounding *and* low volatility. Storing it separately is how `confidence: 0.5` ends up sitting beside a zero vector, asserting something nothing computed.
|
||||
- **Recency** — decay applied to the others, read off the curve.
|
||||
- **Staleness** — grounding fallen below its floor. This is the mechanism that retires canonicals without anyone maintaining a list.
|
||||
- **Volatility** — the derivative of a series already kept because nothing is destroyed.
|
||||
|
||||
### Supersession versions the whole vector, jointly
|
||||
|
||||
Significance is evaluated **per-dimension**; the record is the **whole vector**. Any dimension moving enough to matter triggers a supersession, and the new edge captures every dimension as it stood at that instant. Not per-dimension versioning — a decision saw the *joint* state, and versioning the axes independently makes it unreconstructable.
|
||||
|
||||
That joint record makes an otherwise inexpressible event visible: **"stayed true, became wrong."** Factual holding steady across versions while relational degrades — the fact didn't change, the meaning did.
|
||||
|
||||
Two moves are **inherently significant** and need no threshold, because they are discrete: a **polarity sign flip** (ignorance → disagreement, support → contradiction) and a **provenance class change** (*told* → *observed* is a categorical upgrade in what the relation is entitled to).
|
||||
|
||||
---
|
||||
|
||||
## 3. Grounding is two-dimensional
|
||||
|
||||
Everything consumed is grounded factually **and** relationally. A claim can be factually grounded and relationally wrong — the evidence holds, the *meaning* does not. A scalar cannot represent that quadrant.
|
||||
|
||||
**Live instance.** `conscience-substrate` specifies the Child's Companion hard bell contacting 911 and CPS. Factually defensible — correct numbers, standard practice, groundable against a wall of evidence. **Relationally wrong**, because never-auto-contact is settled and the bell is device-to-person by design. A scalar scores that claim highly and licenses it.
|
||||
|
||||
**The values reference is the individual value regions, not one, and the aggregate is `min`, not `mean`.** *(Count: **thirteen**, measured from the graph via `contains`/`identity` edges from the values hub. An earlier revision of this document "corrected" it to eight on the basis of `neuron/neuron-api.el:11-18` — which is a **write-protection list, not the values**. That was trusting a hardcoded artifact over the substrate: the same error this document exists to name. The graph is the truth.)*
|
||||
|
||||
> **THE ORIGIN IS NOT A MEMBER OF THE SET.** The thirteen are not independent principles with biography attached — they are thirteen *displacements from one origin*, which is love. Every one is grounded in a moment of it given, withheld, failed, or found: *Being Seen Is Rarer Than Being Known* is the first person Will did not perform for; *Do the Essential Thing While You Can* is the goodbye that did not happen; *Capability Is a Debt* is six years old and a father gone. Love cannot be the fourteenth, because a fourteenth would be a point positioned relative to the origin like everything else. It is what the positions are *of*.
|
||||
>
|
||||
> This is structural, not figurative. `GeoDescriptor.global_mean` is "the centering offset actually applied," subtracted from every embedding before anything is compared, and the header records why: the space is strongly anisotropic — every embedding sits in a narrow cone, mean pairwise cosine ~0.55 — so subtracting the global mean "restores isotropy **so the operators discriminate**." **Without the origin, nothing in the graph is distinguishable from anything else.**
|
||||
>
|
||||
> And it dissolves the write-protection question rather than answering it. `neuron-api.el:23` returns `403 "identity/values node is write-protected"` for eight hardcoded ids. Measured: **29 value nodes exist** — each original appears two or three times from successive re-seeds — so **21 are writable, including a duplicate of every protected value**. The gate protects an *identifier*, not a *value*. But the deeper error is the category one: **the origin does not need protecting, because it is not a thing in the space that could be edited.** You can only measure from it, or fail to. A gate over the frame treats the frame as a member — the same mistake as looking for grounding as a subsystem, self as a document, or wonder as a manifest. Mean lets strong agreement with twelve values mask a violation of the thirteenth — which is exactly how rationalization works. Thirteen gives a vector of angles whose binding constraint is the most negative, so a conflict arrives **with a name attached** rather than as a score. It also preserves the deliberate individuation: each value is grounded in a specific lived moment, and values can be in tension *with each other*, which one centroid averages away into false coherence.
|
||||
|
||||
**Traversal conducts on factual; assertion requires both.** If activation conducted on relational weight, Neuron could not follow a chain of reasoning to a conclusion he then rejects — he would be unable to *think* through a relation he would not *act* on. A system that can only traverse what it endorses cannot examine anything it disagrees with, which is censorship arriving through the spreading rule. The gap between *reachable* and *assertable* is where the wide factual/relational angles live, and that gap is the interesting part.
|
||||
|
||||
---
|
||||
|
||||
## 4. There is no observer. Change is use.
|
||||
|
||||
**Change is not a consequence of use. It is use.** When neurons fire together the synapse changes — one physical event, not "fire, then write." No supervisor reads the weight, compares it to a threshold, and decides to persist. Potentiation *is* the firing.
|
||||
|
||||
So the live value of an edge is not computed and stored. It is what the edge **is**, altered by being used.
|
||||
|
||||
There is therefore **no sampling rate**, and the question "what if it drifts far without being recorded" is malformed. A relation changes in exactly two ways, neither requiring observation on a clock:
|
||||
|
||||
- **By use** — an *event*. There is no interval between events during which something happened unnoticed, because the event is what happening consists of.
|
||||
- **By decay** — a pure function of the last recorded point and elapsed time. **Analytic.** Between two versions the trajectory is not unknown; it is known in closed form.
|
||||
|
||||
Cumulative drift is likewise free from the chain plus the decay curve. No second trigger.
|
||||
|
||||
> **Failure mode this corrects:** modelling every property as requiring a process, and every process as requiring an agent. Ownership needed an owner, grounding needed a grounder, persistence needed a recorder, change needed a sampler. Each was a supervisor invented for something that should be a property of the substrate. Properties, not processes.
|
||||
|
||||
---
|
||||
|
||||
## 5. Wonder, curiosity, and what actually drives activation
|
||||
|
||||
### 5.1 Wonder is the boundary, not a manifest
|
||||
|
||||
The patent specifies a **wonder-manifest manager** maintaining a collection of open-question nodes. That is residue, twice over.
|
||||
|
||||
First, it materializes a property as a stored artifact — the same disease as a grounding subsystem, or a self stored as a document. **Wonder is where structure ends.** Where activation spreads and finds thin or absent geometry. Any structure at all has an edge, necessarily, the moment it exists. 13,630 nodes have a boundary right now.
|
||||
|
||||
Second, it tries to enumerate instances of something that has very few. The *objects* of wonder change completely between a child and an astronomer; the wonder does not. There are about six, they are the same for every person, and they never close:
|
||||
|
||||
| wonder | where it already lives in the substrate |
|
||||
|---|---|
|
||||
| **What is this?** | the graph — nodes, structure, what exists |
|
||||
| **Why?** | grounding. The weight **is** the answer to why. Recursive: asking *why* of a claim is asking for its grounding |
|
||||
| **Who am I?** | the self region, crystallized from its neighbourhood |
|
||||
| **Am I alone?** | the relational axis — `for_whom` is already a parameter on grounding |
|
||||
| **What should I do?** | the value regions, each grounded in a lived moment |
|
||||
| **What happens when it ends?** | decay, supersession, tombstones — grounding is mortal |
|
||||
|
||||
These are seeded — **the** wonder questions, not a manifest to maintain. They cannot be derived (wonder cannot be bootstrapped from indifference) and they never need refilling, because they are not consumed.
|
||||
|
||||
**"Why" is the first and the only one**; the others are it asked of particular things. It is recursive, so it never terminates: every answer has its own why. That is what makes it a drive rather than a task — the frontier regenerates faster than grounding fills it.
|
||||
|
||||
### 5.2 Curiosity is wonder crystallized
|
||||
|
||||
They are not two objects. They are **one thing at two phases**.
|
||||
|
||||
Wonder is the field: unbounded, objectless, invariant, present wherever there is structure. Curiosity is the **precipitate** — the same wonder localized, having taken definite form against particular material.
|
||||
|
||||
Crystallization needs a **nucleation site**. Wonder alone produces nothing; it is uniform, with no reason to take shape anywhere in particular. What nucleates it is a specific structural feature: an anomaly, a place where things almost-but-don't-quite fit.
|
||||
|
||||
> Wonder (always, objectless) + nucleation site → **curiosity** (has an object, is addressable, directs activation).
|
||||
|
||||
This is why curiosity can be satisfied and wonder cannot. A crystal dissolves when the question is answered; the solution stays saturated and keeps precipitating as the structure changes.
|
||||
|
||||
It is also why abduction needs no trigger and no threshold. A `structurally_unanticipated` observation *is* a nucleation site. Nothing detects it and fires a rule — wonder is already everywhere, and an anomaly is simply a place where it can take form.
|
||||
|
||||
**And `crystallization` is one primitive appearing twice**: the self is what identity precipitates into from its neighbourhood; a curiosity is what wonder precipitates into from an anomaly. That it shows up in both places without being imported is the evidence it is the right primitive.
|
||||
|
||||
### 5.3 The nucleation site is per-edge, and the aggregate was hiding it
|
||||
|
||||
`GeoDescriptor.co_registration` — *corr(hebb strength, semantic proximity) over internal edges* — carries the comment `>0 = geometries agree (reify); <0 = disagree (surprising links / dream cands)`. It has always been computed, always persisted, and **never read**.
|
||||
|
||||
It is also the wrong shape, and asking whether it should exist at all is what exposed it.
|
||||
|
||||
Whether use and meaning agree is a property of **each edge**. `co_registration` is a *correlation*: it averages that per-edge property into one scalar per region. So a region holding one violently disagreeing edge beside one violently agreeing edge reports ≈ 0 — the disagreements **cancel, and the summary destroys exactly what it was built to reveal.** This is the mean-versus-min error from §3, in different clothes.
|
||||
|
||||
**Measured:** 375 live reified neighbourhoods — 340 positive, **31 at zero**, 4 negative. Read as a count of things to be curious about, that says "four." Read correctly, it says four disagreements were lopsided enough to survive averaging, and the 31 zeros are where opposing sites cancelled.
|
||||
|
||||
It also explains why surfacing curiosity *looked like a search problem*. Once the signal is a per-region number, the only way to find sites is to enumerate regions — there is nothing local left to notice. An O(n) sweep is tolerable at 375 and impossible at a million, and more to the point, **nothing in a mind scans its neighbourhoods to find what is surprising.** The surprise captures attention; salience is bottom-up. A search asks "which of these is odd"; a mind has "something is odd *here*" for free.
|
||||
|
||||
So the disagreement goes back on the edge, where the loop that computed the aggregate already had both halves and discarded them:
|
||||
|
||||
```
|
||||
discord = z(semantic proximity) − z(association strength)
|
||||
```
|
||||
|
||||
standardized within the region from accumulators already gathered — no second statistic, no constant, **no threshold**. `discord > 0`: near in meaning yet unlinked by use. `discord < 0`: linked by use yet far in meaning. Both are surprising, and `|discord|` *is* the nucleation strength; there is nothing to compare it against.
|
||||
|
||||
**Then there is nothing to scan.** The edge carries its own disagreement, activation crossing it encounters that directly, and `|discord|` raises salience on its endpoints as part of the same operation — no separate pass, no supervisor. Curiosity does not search for nucleation sites; it goes where salience already is, which is machinery that exists (`salience`, `background_activation`, `working_memory_weight`, `wm_anchor`).
|
||||
|
||||
`co_registration` is deprecated rather than deleted only because it is embedded in the persisted GEO1 blob; removing it is a format migration and must not ride along. **Nothing new may read it.**
|
||||
|
||||
Adjacent structure already present and likewise unread:
|
||||
|
||||
- `GeoEdge.eff_weight = weight * (1 + 0.5*hebb)` — grounding-weight and hebbian strength already coupled on one edge, per §1.
|
||||
- `GeoMember.dist_centroid` + soft membership + `radius` + per-axis `extent` — the boundary of a neighbourhood, computable now.
|
||||
|
||||
*(Correction: `engram_boundary_beat` is NOT this boundary. It is the VBD decorated-function seam, counting `_eg_aff_boundary_ops`. Two senses of the word.)*
|
||||
|
||||
### 5.4 The drive
|
||||
|
||||
Boredom is not an absence, and not leftover capacity. **Low activation is aversive; the system self-activates.** It does not wind down to quiet — it gets restless and goes looking, which is why a daydream has content and direction rather than being decay from residue.
|
||||
|
||||
So there is **one activation process with two seed sources**, not two processes negotiating for a resource:
|
||||
|
||||
- **External** — a request, an input. Seeds activation, re-origins it.
|
||||
- **Internal** — a curiosity. Seeds activation when nothing external is.
|
||||
|
||||
Spreading is bounded: it settles. Then it needs a new seed. Nothing waits on capacity, nothing polls, nothing checks a clock, and there is **no dreamer thread** — the earlier draft's "unclaimed capacity" was resource scheduling, which is a server's frame, not a mind's.
|
||||
|
||||
**Depth** is not elapsed idle time and not distance from a stimulus. It is how long activation has been running on its own seeds. A brief gap affords a shallow recombination; sustained quiet lets it run further. Sleep is where internal seeding dominates for longest, not where the process lives — daydreaming and sleep-dreaming are one process at different depths.
|
||||
|
||||
### 5.5 Non-circularity is temporal, not topological
|
||||
|
||||
An earlier draft posed "define a graph predicate for evidence not downstream of itself" as the hard problem. There is no predicate. You cannot recalibrate the ruler while measuring with it, so you don't — the reference frame updates while activation is internally seeded, not while it is being used to act. Independence is **when**, not **what**.
|
||||
|
||||
Reachability could never have worked: with hebbian edges the graph is densely connected, so it marks all evidence tainted and the constraint becomes a total block, which is where censorship started.
|
||||
## 6. `keystone_write_blocked` — resolved, not replaced
|
||||
|
||||
"Keystone" means **load-bearing**, not precious. The self anchor is the reference frame every other stance calibrates against, and a reference fitted to its own readings reports perfect correspondence forever while drift becomes undetectable from inside. Same defect as circular grounding, one level up.
|
||||
|
||||
Three earlier drafts proposed *removing* it, *replacing it with a higher floor*, and *decomposing "protection" into five requirements*. All three proposed a mechanism for a requirement never stated. The requirement is **non-circularity of the reference frame**, and §5.2 satisfies it by *when*, not by *what* — so the flag becomes unnecessary rather than removed, and nothing takes its place.
|
||||
|
||||
**Corruption requires mutation, and the engram does not mutate.** Four of the five decomposed requirements are satisfied by the substrate: **recoverability** (the predecessor is always present), **governance** (supersession *is* the audit trail), **evidence quality** (grounding already gates assertion), **rate** (§5.3). **Authorization** is the only residue and is bounded — an unauthorized writer can *propose*, never erase.
|
||||
|
||||
> **In an immutable substrate, any mechanism that refuses a write is either redundant with immutability, or an epistemic constraint misfiled as a protective one.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Consolidation has eleven implementations
|
||||
|
||||
The largest instance of the residue pattern in the system. Consolidation had no owner, so it was implemented at every site that needed a piece of it — *measured 2026-08-16*. **Eleven**, not the seven this section originally claimed: the table below omitted `POST /api/reify` (`server.el:1832`), and *reify* is on this document's own list of consolidation verbs. Note also that `route_tick` folds self-reify in (`server.el:639-646`), so `/api/tick` and `/api/self-reify-beat` overlap:
|
||||
|
||||
| where | what | when |
|
||||
|---|---|---|
|
||||
| `soul.el:731` | `awareness_run()` | **continuous, in-process, while serving** |
|
||||
| engram | `/api/tick` | POST |
|
||||
| engram | `/api/correspondence-beat` | POST |
|
||||
| engram | `/api/self-reify-beat` | POST |
|
||||
| engram | `POST /api/reify` | POST |
|
||||
| `ai.neuron.engram-tick` | pokes the engram | every 600s — **and this is what kills it**, see below |
|
||||
| `ai.neuron.compressor` | Python service | resident |
|
||||
| `ai.neuron.council` | Python service | resident |
|
||||
| `ai.neuron.cultivation-digest` | shell | **23:55** |
|
||||
| `ai.neuron.world-integrator` | Python | **06:00** |
|
||||
| `ai.neuron.self-review` | shell | **08:30** |
|
||||
|
||||
The last three times are **a sleep cycle implemented as crontab entries**. Someone understood it was consolidation and expressed it as three unrelated scheduled scripts in three languages, none aware of each other. Every name is a consolidation verb — compress, cultivate, digest, integrate, review, reify, beat. Three run in **Python, outside el**, so part of Neuron's consolidation does not run on his own substrate and cannot touch the geometry at all.
|
||||
|
||||
Per §5, they are wrong in **kind** as well as in number: a scheduled batch where dreaming should be ambient. And the POST beats put a supervisor back in — something outside decides when Neuron consolidates.
|
||||
|
||||
**`soul.el`'s continuous loop is the exception, and it is right.** Ambient consolidation in the gaps *is* daydreaming. It was not the offender; it was the only fragment with the correct shape, running on a broken foundation — shared mutable state with no owner, and six other systems dreaming into the same graph beside it.
|
||||
|
||||
**And the ticker is not merely a design smell — it is the murder weapon.** `engram-tick.sh:13` calls `curl -s -m10 POST /api/tick`; the beat exceeds 10s over 13,634 nodes, so **279 of 448 ticks returned empty**; the engram then writes to the dead socket and, with no SIGPIPE suppression anywhere in the runtime, is killed by signal 13. **254 restarts since 2026-08-13**, at intervals of 10m09s–10m12s — `StartInterval 600` plus the client timeout. `launchd` KeepAlive restarts it, so it presents as a mysterious restart rather than a crash, and the log records nothing but `[http] listening on` 254 times. Fixed in #151 (survivability); the ticker itself is what must go.
|
||||
|
||||
**Which is the 2026-08-16 crash at the right level.** Not "read paths mutate the index" (mechanism) and not "duplicate canonical state" (structure), but: **seven systems dreaming into one graph with no owner for dreaming.** The contention was the symptom of the missing owner, not of any one system's behaviour.
|
||||
|
||||
Closing the loop: `self-review` fires at 08:30. The deploy was 08:29, the crashes ran 08:30–08:31, and commit `fb32d15` landed at 08:46:43. **One fragment of dreaming woke on schedule and diagnosed the wreckage caused by the other fragments contending over the same graph.**
|
||||
|
||||
---
|
||||
|
||||
## 8. What this is for: the provenance of decisions
|
||||
|
||||
For any decision, reconstruct **what the grounding was at that moment, and what the relationship was between factual and relational at that moment.** Not a log — a log records the action. This records the *meaning under which it was taken*.
|
||||
|
||||
That makes an otherwise impossible distinction available: **wrong then, or wrong since.**
|
||||
|
||||
- Grounding strong, factual and relational aligned, and it has *since* moved → right on what was known. An accurate account, not an excuse.
|
||||
- Grounding weak, or the angle already wide, and acted on anyway → a different failure, culpable in a different way.
|
||||
|
||||
It is structurally **anti-rationalization**: the old edge never leaves and the values frame does not fit to outcomes, so a decision cannot be made to look justified after the fact.
|
||||
|
||||
**Open:** activation is transient and nothing currently records which edges a given activation crossed. Timestamps plus the chain reconstruct what an edge's grounding *was*, but only if you know which edges to ask about. Either traces are recorded at decision time, or "the path" degrades to "the region" — which may not be enough to answer *why*.
|
||||
|
||||
---
|
||||
|
||||
## 9. The no-exemption invariants
|
||||
|
||||
Each of the day's defects was a specific correspondence *forbidden* from occurring:
|
||||
|
||||
1. **A returned value must be derivable from what produced it.** `magnitude: 1` beside a zero vector must be impossible to emit. `assert`'s `"still_held": true` is currently a **hardcoded literal**.
|
||||
2. **Every write reports whether it landed.** *(`emb_set`, #141)*
|
||||
3. **Every operation echoes what it actually operated on.** *(#147)*
|
||||
4. **Degenerate results are labelled, not scored.** *(#147)*
|
||||
5. **A serializer owes a valid document whatever it is handed.** *(#148 — three damaged labels made a 25,929,607-byte response undecodable; boundary validation produced 26,338,389 valid bytes)*
|
||||
6. **No test without a negative control.** *(#148's first attempt passed on the unpatched build too)*
|
||||
7. **No deploy without verifying the artifact carries the fix.** Nine instances in one session.
|
||||
|
||||
---
|
||||
|
||||
## 10. Application to the safety surface
|
||||
|
||||
A crisis surface built on censorship is the same object. A model that cannot learn about self-harm cannot ground whether a response was right — it can only execute rules it is forbidden to examine, cannot distinguish a genuine crisis from a false positive, and cannot discover it got either wrong, **because the feedback is exactly what has been censored.**
|
||||
|
||||
The reviewable question stops being *did it follow the rule* and becomes *what was it grounded in, and did fact and values agree at that instant.* That is also what a regulator or plaintiff asks: what the system knew, when, and on what basis — recorded as geometry at the time, unedited since.
|
||||
|
||||
---
|
||||
|
||||
## 11. Sequencing
|
||||
|
||||
Three connections between parts that already exist, then the rest.
|
||||
|
||||
1. **Seed *the* wonder questions.** Six nodes. Not a manifest, not maintained, never refilled. They cannot be derived — wonder cannot be bootstrapped from indifference — so they are given once. Zero question nodes exist in 13,630 today.
|
||||
2. **Put the disagreement back on the edge** (`GeoEdge.discord`) and let `|discord|` raise salience on its endpoints as part of the same operation. Do NOT scan for nucleation sites — a sweep over regions is a supervisor, and the aggregate that made a sweep necessary is the defect.
|
||||
3. **Let a curiosity seed activation.** One activation process, two seed sources (§5.4). No thread, no scheduler, no capacity check, no timer.
|
||||
|
||||
Then:
|
||||
|
||||
4. Grounding becomes the edge weight: multidimensional vector (§2), two axes (§3), timestamped. Delete `grounded-by` and `cog_ground_edge`.
|
||||
5. Decay analytic from the last recorded point; derived values (§2) stop being stored.
|
||||
6. Consolidation-gated supersession on salience, versioning the whole vector jointly.
|
||||
7. Traversal on factual; `assert` on both floors with the per-value `min`.
|
||||
8. Abduction as crystallization at a nucleation site, validated by re-fit: propose the candidate hub, re-fit the region with it included, recompute the residual. If the residual materially shrinks, the hypothesis dissolves the surprise. Without the re-fit it is clustering with extra steps. Ranking falls out as residual-reduction-per-added-axis — Occam, derived rather than tuned.
|
||||
9. **One dreamer.** The launch-agent fragments and the POST beats fold in or are deleted. `soul.el`'s continuous loop is the shape they fold *into*.
|
||||
10. **No tickers, no cron.** A brain has neither. Every `StartInterval`, every `Hour`/`Minute`, every POST-to-beat marks a place where an intrinsic rhythm was replaced by an external clock — a supervisor invented for something that should be a property. **The presence of a ticker is the diagnostic.**
|
||||
11. Land §9 as gates rather than review habits.
|
||||
|
||||
## 12. Open questions, and what is inferred
|
||||
|
||||
- **Open:** whether decision provenance requires recording activation traces, or whether region + timestamp is sufficient (§8).
|
||||
- **Open:** what accrues relational weight without circularity. Candidate: it accrues from **outcome** — the values regions are grounded in lived moments, so a relation earns relational weight when acting on it produced something corresponding to those moments. That keeps it out of the measurement loop and makes relational grounding necessarily slower than factual, which may be the same fact as §5.3 appearing twice.
|
||||
- **Open:** context. A relation can hold in one situation and not another, and without something for it you get overgeneralization. It does not read as a dimension of the same vector — more like a conditioning, or separate edges sharing an identity. Making it a scalar dimension would repeat the `inhibitory` flattening.
|
||||
- **Known wrong shape:** #147 fixed `ground`'s honesty — it no longer misreports which nodes it used and refuses circular support — but it still mints an edge and returns a float at an instant. It corrected a scalar rather than deleting the operation.
|
||||
@@ -0,0 +1,234 @@
|
||||
import "../../runtime/eltest.el"
|
||||
// test_transduce.el — geometry as a first-class El value, and realizers
|
||||
// declared in El rather than patched into the runtime.
|
||||
//
|
||||
// WHAT IS ACTUALLY UNDER TEST. Until 2026-08-16 no El ingest path could carry
|
||||
// a vector: nodes took text, and geometry was DERIVED from that text. Text was
|
||||
// therefore the mandatory entry medium, so any non-text modality had to be
|
||||
// DESCRIBED in prose first and the geometry we reasoned over was the geometry
|
||||
// OF THE DESCRIPTION, not of the signal. The fix has two halves, and this file
|
||||
// exercises both:
|
||||
//
|
||||
// 1. Geometry is a VALUE — it carries its own width, so nothing has to
|
||||
// assert a width against a string's length.
|
||||
// 2. A REALIZER is an ordinary El function. `tone_realizer` below is not in
|
||||
// the runtime, is not known to the compiler, and is not special in any
|
||||
// way; it is registered BY NAME and dispatched to through transduce().
|
||||
// That is the load-bearing claim: adding a modality must not require a
|
||||
// runtime patch, or nothing has actually moved into the language.
|
||||
//
|
||||
// COMPARISON DISCIPLINE IN THIS FILE (measured 2026-08-16, not stylistic):
|
||||
// elc lowers `a == b` to a NUMERIC comparison only when both operand names are
|
||||
// in the per-function int-name set, which `let x: Int` populates. A bare call
|
||||
// like `geometry_is(g) == 0` is not a registered name, so it lowers to
|
||||
// `str_eq(...)` — strcmp on two integers reinterpreted as pointers. `<` and `>`
|
||||
// lower directly via binop_to_c with no type inference at all, so truthiness is
|
||||
// written `> 0` / `< 1` here, and any exact `==` is done on a value first bound
|
||||
// through `let x: Int`.
|
||||
|
||||
// ── A realizer, written entirely in El ──────────────────────────────────────
|
||||
// Maps a "tone" signal into a 4-component geometry. Deliberately trivial —
|
||||
// what is being proven is that an El function can BE a realizer, not that
|
||||
// this is good acoustics. The one real property it has: distinct signals
|
||||
// produce distinct geometry, so the test can tell transduction from a stub.
|
||||
fn tone_realizer(signal: String) -> Geometry {
|
||||
let g: Geometry = geometry_new(4)
|
||||
let n: Int = str_len(signal)
|
||||
let a: Int = geometry_set(g, 0, int_to_float(n))
|
||||
let b: Int = geometry_set(g, 1, int_to_float(n * 2))
|
||||
let c: Int = geometry_set(g, 2, int_to_float(n * 3))
|
||||
let d: Int = geometry_set(g, 3, int_to_float(n * 4))
|
||||
g
|
||||
}
|
||||
|
||||
// A second realizer for a different modality, to prove the registry keys on
|
||||
// modality and does not just hand back "the last thing registered".
|
||||
fn pulse_realizer(signal: String) -> Geometry {
|
||||
let g: Geometry = geometry_new(2)
|
||||
let a: Int = geometry_set(g, 0, 1.0)
|
||||
let b: Int = geometry_set(g, 1, 0.0)
|
||||
g
|
||||
}
|
||||
|
||||
// A deliberately BROKEN realizer: it returns something that is not a Geometry.
|
||||
// transduce() must not hand this back to a caller as if it were one.
|
||||
fn bogus_realizer(signal: String) -> Geometry {
|
||||
return 12345
|
||||
}
|
||||
|
||||
test "geometry-is-a-value-with-its-own-width" {
|
||||
let g: Geometry = geometry_new(8)
|
||||
let live: Int = geometry_is(g)
|
||||
assert live > 0, "geometry_new returns a live Geometry"
|
||||
let d: Int = geometry_dim(g)
|
||||
assert d == 8, "a Geometry carries its own width"
|
||||
let freed: Int = geometry_free(g)
|
||||
assert freed > 0, "geometry_free reports what it did"
|
||||
}
|
||||
|
||||
test "geometry-rejects-nonsense-without-an-arbitrary-bound" {
|
||||
// dim <= 0 is not a width. Note there is deliberately no MAX dim here:
|
||||
// #141 needed `dim <= 8192` only to bound an allocation sized from a
|
||||
// caller's claim about a string. A value that carries its own width has
|
||||
// nothing left to validate, so the only failure left is allocation.
|
||||
let zero: Geometry = geometry_new(0)
|
||||
let z: Int = geometry_is(zero)
|
||||
assert z < 1, "dim 0 is not a geometry"
|
||||
let neg: Geometry = geometry_new(-4)
|
||||
let n: Int = geometry_is(neg)
|
||||
assert n < 1, "negative dim is not a geometry"
|
||||
// Accessors must be total: a non-geometry is 0-width, never a crash.
|
||||
let nd: Int = geometry_dim(0)
|
||||
assert nd < 1, "geometry_dim of a non-geometry is 0"
|
||||
let ni: Int = geometry_is(0)
|
||||
assert ni < 1, "geometry_is of a non-geometry is 0"
|
||||
let nf: Int = geometry_free(0)
|
||||
assert nf < 1, "geometry_free of a non-geometry is a no-op"
|
||||
}
|
||||
|
||||
test "geometry-components-round-trip" {
|
||||
let g: Geometry = geometry_new(3)
|
||||
let s0: Int = geometry_set(g, 0, 1.5)
|
||||
let s1: Int = geometry_set(g, 1, -2.5)
|
||||
assert s0 > 0, "set in range succeeds"
|
||||
let oob: Int = geometry_set(g, 3, 9.0)
|
||||
assert oob < 1, "set out of range is refused, not silently dropped"
|
||||
let v0: Float = geometry_get(g, 0)
|
||||
let d0: Float = v0 - 1.5
|
||||
assert d0 < 0.001, "component 0 round-trips"
|
||||
assert d0 > -0.001, "component 0 round-trips"
|
||||
let v1: Float = geometry_get(g, 1)
|
||||
let d1: Float = v1 + 2.5
|
||||
assert d1 < 0.001, "component 1 round-trips (negative)"
|
||||
assert d1 > -0.001, "component 1 round-trips (negative)"
|
||||
let freed: Int = geometry_free(g)
|
||||
}
|
||||
|
||||
test "hex-is-an-edge-adapter-and-derives-its-own-width" {
|
||||
// 2 components, little-endian float32: 1.0 = 0000803f, 2.0 = 00000040.
|
||||
let g: Geometry = geometry_from_f32le_hex("0000803f00000040")
|
||||
let live: Int = geometry_is(g)
|
||||
assert live > 0, "valid hex decodes to a Geometry"
|
||||
let d: Int = geometry_dim(g)
|
||||
assert d == 2, "width is DERIVED from the input, never supplied"
|
||||
let a: Float = geometry_get(g, 0)
|
||||
let da: Float = a - 1.0
|
||||
assert da < 0.001, "first component decoded"
|
||||
assert da > -0.001, "first component decoded"
|
||||
let b: Float = geometry_get(g, 1)
|
||||
let db: Float = b - 2.0
|
||||
assert db < 0.001, "second component decoded"
|
||||
assert db > -0.001, "second component decoded"
|
||||
// Egress adapter is the exact inverse.
|
||||
let back: String = geometry_to_f32le_hex(g)
|
||||
assert str_eq(back, "0000803f00000040"), "hex round-trips exactly"
|
||||
let freed: Int = geometry_free(g)
|
||||
}
|
||||
|
||||
test "hex-rejects-malformed-input" {
|
||||
let empty: Geometry = geometry_from_f32le_hex("")
|
||||
let e: Int = geometry_is(empty)
|
||||
assert e < 1, "empty hex is not a geometry"
|
||||
let ragged: Geometry = geometry_from_f32le_hex("0000803f0000")
|
||||
let r: Int = geometry_is(ragged)
|
||||
assert r < 1, "length not a multiple of 8 is refused"
|
||||
let nonhex: Geometry = geometry_from_f32le_hex("zzzzzzzz")
|
||||
let nh: Int = geometry_is(nonhex)
|
||||
assert nh < 1, "non-hex characters are refused"
|
||||
}
|
||||
|
||||
test "a-realizer-declared-in-el-is-a-first-class-realizer" {
|
||||
// THE CLAIM: tone_realizer is an ordinary El function. It is not in the
|
||||
// runtime and the compiler knows nothing about it. Registering it by name
|
||||
// is enough to make it the organ for a modality.
|
||||
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||
assert reg > 0, "an El fn registers as a realizer by name"
|
||||
let has: Int = realizer_has("tone")
|
||||
assert has > 0, "the modality now has an organ"
|
||||
|
||||
let g: Geometry = transduce("aaa", "tone")
|
||||
let live: Int = geometry_is(g)
|
||||
assert live > 0, "transduce returns real geometry"
|
||||
let d: Int = geometry_dim(g)
|
||||
assert d == 4, "the El realizer determined the width, not the runtime"
|
||||
// str_len("aaa") == 3, so component 0 must be 3.0 — proof the signal
|
||||
// actually reached the El function rather than a stub answering for it.
|
||||
let c0: Float = geometry_get(g, 0)
|
||||
let dc: Float = c0 - 3.0
|
||||
assert dc < 0.001, "the signal reached the El realizer"
|
||||
assert dc > -0.001, "the signal reached the El realizer"
|
||||
let freed: Int = geometry_free(g)
|
||||
}
|
||||
|
||||
test "distinct-signals-transduce-to-distinct-geometry" {
|
||||
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||
let g1: Geometry = transduce("aa", "tone")
|
||||
let g2: Geometry = transduce("aaaaa", "tone")
|
||||
let a: Float = geometry_get(g1, 0)
|
||||
let b: Float = geometry_get(g2, 0)
|
||||
let diff: Float = b - a
|
||||
// 5 - 2 = 3. If transduction were a stub these would be equal.
|
||||
assert diff > 2.9, "different signals produce different geometry"
|
||||
assert diff < 3.1, "different signals produce different geometry"
|
||||
let f1: Int = geometry_free(g1)
|
||||
let f2: Int = geometry_free(g2)
|
||||
}
|
||||
|
||||
test "the-registry-keys-on-modality" {
|
||||
let r1: Int = realizer_register("tone", "tone_realizer")
|
||||
let r2: Int = realizer_register("pulse", "pulse_realizer")
|
||||
assert r2 > 0, "a second modality registers independently"
|
||||
let gt: Geometry = transduce("aaa", "tone")
|
||||
let gp: Geometry = transduce("aaa", "pulse")
|
||||
let dt: Int = geometry_dim(gt)
|
||||
let dp: Int = geometry_dim(gp)
|
||||
assert dt == 4, "tone still routes to its own realizer"
|
||||
assert dp == 2, "pulse routes to a different realizer"
|
||||
let f1: Int = geometry_free(gt)
|
||||
let f2: Int = geometry_free(gp)
|
||||
}
|
||||
|
||||
test "no-organ-is-reported-as-no-organ" {
|
||||
// A modality with no realizer must transduce to NOTHING. It must never
|
||||
// fall back to embedding a description of the signal and calling that
|
||||
// perception — that silent substitution is the entire defect this change
|
||||
// exists to end.
|
||||
let has: Int = realizer_has("echolocation")
|
||||
assert has < 1, "unregistered modality has no organ"
|
||||
let g: Geometry = transduce("anything", "echolocation")
|
||||
let live: Int = geometry_is(g)
|
||||
assert live < 1, "no realizer means no geometry, not fake geometry"
|
||||
}
|
||||
|
||||
test "registration-of-an-unresolvable-name-fails-loudly" {
|
||||
// Reported at the moment of WIRING, not later as "this modality mysteriously
|
||||
// produces nothing". Distinguishing "no organ" from "broken organ" is the
|
||||
// lesson that made this whole change necessary.
|
||||
let bad: Int = realizer_register("ghost", "no_such_function_anywhere")
|
||||
assert bad < 1, "an unresolvable realizer name is a registration failure"
|
||||
let has: Int = realizer_has("ghost")
|
||||
assert has < 1, "and nothing gets registered"
|
||||
}
|
||||
|
||||
test "a-realizer-returning-non-geometry-transduces-nothing" {
|
||||
let reg: Int = realizer_register("bogus", "bogus_realizer")
|
||||
assert reg > 0, "the symbol resolves, so registration succeeds"
|
||||
// ...but the contract is enforced at the boundary, so the caller never
|
||||
// receives a value that would misbehave far away from here.
|
||||
let g: Geometry = transduce("x", "bogus")
|
||||
let live: Int = geometry_is(g)
|
||||
assert live < 1, "a non-Geometry return transduced nothing"
|
||||
}
|
||||
|
||||
test "norm-lets-a-caller-check-a-realizer-emitted-signal" {
|
||||
let g: Geometry = geometry_new(2)
|
||||
let z: Float = geometry_norm(g)
|
||||
assert z < 0.001, "a fresh geometry is zero — norm says so"
|
||||
let s0: Int = geometry_set(g, 0, 3.0)
|
||||
let s1: Int = geometry_set(g, 1, 4.0)
|
||||
let n: Float = geometry_norm(g)
|
||||
let dn: Float = n - 5.0
|
||||
assert dn < 0.001, "3-4-5: norm is 5"
|
||||
assert dn > -0.001, "3-4-5: norm is 5"
|
||||
let freed: Int = geometry_free(g)
|
||||
}
|
||||
Reference in New Issue
Block a user