Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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")
|
||||
}
|
||||
+465
-64
@@ -5960,6 +5960,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 +8866,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 +14256,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 +14351,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 +14381,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
|
||||
@@ -613,10 +667,22 @@ 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);
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
# Correspondence, Grounding, and the Provenance of Decisions
|
||||
|
||||
**Status:** design, not yet built
|
||||
**Date:** 2026-08-16
|
||||
**Scope:** `lang/runtime/engram_cognition.{c,h}`, `engram_verify.c`, `el_runtime.c` (think / beat / ground / assert), `engram/src/server.el`
|
||||
**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. The effect, and the Ishikawa
|
||||
|
||||
**Effect observed:** all five cognitive faculties (`reason`, `abduce`, `induce`, `plan`, `analogize`) return byte-identical results, differing only in their label.
|
||||
|
||||
### Method
|
||||
- Faculty is selected by a caller-supplied **string**. Abduction is not a choice a caller makes; it is a response to a detected state. *(push where it must be pull)*
|
||||
- All five route through one operation, `engram_think`, which returns a gradient — a read.
|
||||
- The correspondence-beat exists, works, and its result is wired to nothing.
|
||||
|
||||
### Machine
|
||||
- `engram_think(region, anchor, stance, out)` returns `GeoGradient`. **There is no way to express "propose a region."** Abduction is inexpressible in the signature. *(measured, from the struct)*
|
||||
- The only levers are `axis_gain[]`, `ext_floor`, `bias_dir` — all of which warp a read.
|
||||
- `ext_floor` does double duty: it scales the orthogonal residual term *and* floors the in-subspace denominators. *(measured)*
|
||||
|
||||
### Material
|
||||
- The anchor was passed as `NULL`, so every read was taken at the region centroid — the one point where the gradient is zero by construction. *(fixed, #142)*
|
||||
- The stance was never loaded, so every beat's calibration was written and discarded. *(fixed, #146)*
|
||||
- Near-duplicate seeds collapse into one region, understating residual. *(measured, #147: four co-created nodes → one region, groundings 0.93–0.99)*
|
||||
|
||||
### Measurement
|
||||
- Brier before/after is the only error signal in the system, computed inside the beat and surfaced to no consumer. **Measured: 28.11% reduction on a normal region (0.00458568 → 0.00329654, n_trials 6000, reliability 0.930726); 0.00% and n_trials 0 on the keystone.**
|
||||
- `confidence` conflated *calibrated* with *uninformed* until `stance_resumed` was added. *(#146)*
|
||||
- `assert` returns `"still_held": true` **hardcoded** — a temporal property named in the API and answered without consulting anything. *(measured)*
|
||||
- No invariant check anywhere: `magnitude: 1` alongside a zero direction vector is arithmetically impossible and went unflagged for a day.
|
||||
|
||||
### Environment
|
||||
- Production ran none of the day's fixes, so two independent agents' "think is still degenerate" reports were measurements of a stale binary. *(measured)*
|
||||
|
||||
### Man
|
||||
- The problem was derived from the implementation — three levers, therefore one axis of freedom — and the design question was posed inside a space the code invented rather than one the problem defines.
|
||||
|
||||
### Convergence
|
||||
|
||||
Cutting any single branch leaves the effect standing. Fix the Machine alone and callers still invoke `abduce` when nothing is surprising. Fix the Method alone and abduction triggers correctly but returns a direction where it owes a hypothesis. They are one root seen twice, and it is downstream of §0.
|
||||
|
||||
---
|
||||
|
||||
## 2. What `keystone_write_blocked` was actually for
|
||||
|
||||
Three drafts of this section were wrong, and how they were wrong is instructive.
|
||||
|
||||
1. **Remove it** — censorship is never protection.
|
||||
2. **Replace it with a higher grounding floor** — identity should be hard to change, not impossible.
|
||||
3. **Decompose "protect the identity region"** into recoverability, authorization, evidence quality, rate, and governance.
|
||||
|
||||
The first two proposed mechanisms without asking what requirement they served — inventing a requirement, then satisfying it, which is how the flag arrived. The third was right to decompose but still treated *protection* as the requirement.
|
||||
|
||||
**It is not a protection requirement. It is an epistemic one.**
|
||||
|
||||
"Keystone" is not a synonym for *precious*. It is **load-bearing**. The self anchor is the **reference frame**: every other region's stance is calibrated relative to it. If the keystone calibrates against the same measurements it is used to judge, the result is **circular calibration** — the ruler adapting to fit what it measures. After that, everything fits, always, and drift becomes undetectable. Not because the world stopped moving, but because the instrument moved with it.
|
||||
|
||||
This is the same defect as circular grounding, one level up. In #147, `ground` scored a claim against the hub of its own region and returned 0.98 — *circular support is not support.* Here it would be a reference frame fitted to its own readings, reporting perfect correspondence forever. Both produce a confident number with nothing behind it, and both are invisible from inside.
|
||||
|
||||
**So the requirement is: correspondence needs a reference that does not move to fit the measurement.**
|
||||
|
||||
And the block is the right requirement implemented as a prohibition, which is why §0 still bites. Refusing to learn about the ruler does not make it a good ruler — it makes it unexaminable. You trade circular calibration for an ungroundable reference: the same epistemic hole, moved.
|
||||
|
||||
**The resolution is two loops, distinguished by provenance rather than permission.**
|
||||
|
||||
- **Fast loop:** stances calibrate against the keystone. Unrestricted.
|
||||
- **Slow loop:** the keystone calibrates against **independent** evidence — lived moments, the imprint, the grounded instances each value already carries — never from the outcomes it is used to judge.
|
||||
|
||||
Different evidence *source*, not different *authority*. The thing to build is therefore not a flag with better semantics but the general constraint that a region may not be calibrated by evidence downstream of itself.
|
||||
|
||||
---
|
||||
|
||||
## 3. Immutability makes self-corruption impossible — the rest falls out
|
||||
|
||||
**Corruption requires mutation. The engram does not mutate.** Every state is retained; supersession adds, never overwrites. "Corrupt the self" is not a risk that is mitigated — it is a sentence that does not parse against this substrate. It would require erasing a prior self-state, and there is no erase.
|
||||
|
||||
Four of §2's five decomposed requirements are therefore satisfied by the substrate itself:
|
||||
|
||||
| requirement | resolution |
|
||||
|---|---|
|
||||
| **Recoverability** | free — the predecessor is always present. A property, not a policy. |
|
||||
| **Governance** | free — supersession *is* the audit trail. Review needs a history, not a gate, and the history is unavoidable. |
|
||||
| **Evidence quality** | free — grounding already gates assertion. Noise can enter and still not be able to speak. |
|
||||
| **Rate** | free — "lurching" only matters if change is destructive. In an immutable store a lurch is a visible, reversible, fully attributed sequence. Velocity is a comfort concern, not a correctness one. |
|
||||
| **Authorization** | the only residue, and bounded: an unauthorized writer can *propose*, never erase. The question becomes "whose supersession governs," not "who may write." |
|
||||
|
||||
Which gives a general law:
|
||||
|
||||
> **In an immutable substrate, any mechanism that refuses a write is either redundant with immutability, or it is an epistemic constraint misfiled as a protective one.**
|
||||
|
||||
`keystone_write_blocked` is the second kind. It solved corruption — which cannot occur — using censorship, which costs the ability to ever ground the self, while the real constraint (§2) went unaddressed.
|
||||
|
||||
---
|
||||
|
||||
## 4. Grounding is two-dimensional
|
||||
|
||||
Everything consumed is grounded, and grounded in two ways: **factually** and **relationally**. A claim can be factually grounded and relationally wrong — the evidence holds, the *meaning* does not.
|
||||
|
||||
`ground` currently returns **one scalar**. It verifies a claim's centroid against an evidence region: factual correspondence only. There is no values axis. (`for_whom` is the nearest existing hook, but that is an audience, not a values frame.) A single number cannot distinguish *true and meaningful* from *true and misapplied*, and `assert` gates on a single `floor` — so a well-evidenced claim earns the right to be asserted regardless of whether it means the right thing.
|
||||
|
||||
**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 grounding scores that claim highly and licenses it. Only the values axis catches it.
|
||||
|
||||
**And this is the keystone's requirement stated properly.** The values frame must not be fitted to outcomes. If the relational reference recalibrates against the readings it judges, whatever happened becomes what should have happened — correspondence perfect and permanent. That is not learning, it is **rationalization**, and from the inside it is indistinguishable from good calibration. A person whose values adjust to make their conduct correct has no values; they have a record of their conduct.
|
||||
|
||||
- **Factual grounding** fits to evidence. Updates freely and fast. Nothing exempt.
|
||||
- **Relational grounding** updates too — from independent evidence, never from the outcomes it judges.
|
||||
|
||||
---
|
||||
|
||||
## 5. Grounding is a gradient, and it moves
|
||||
|
||||
`engram_think`'s own comment says it returns *"a GRADIENT … never a point."* Grounding owes the same shape and for the same reason.
|
||||
|
||||
**Direction and magnitude, not a score.** Not "how much does this hold" but "in which direction, and how far, does the evidence move this claim." The direction says *what would have to change* for the claim to be better grounded. A scalar discards that and keeps its least informative projection.
|
||||
|
||||
**Two gradients in one space, and the angle between them is the meaning.** Cosine near +1: evidence and values push the same way. Near zero or negative: **factually true, relationally wrong** — now a measured quantity rather than something a careful reader has to notice. The 911/CPS contradiction becomes detectable rather than reviewable.
|
||||
|
||||
**It decays.** Grounding takes the dynamics the substrate already runs on memory: `base_level`, `temporal_decay_rate`, the `access_ts` ring, BLL, `last_fired` on edges. Established once and never revisited, grounding *loses* magnitude — confidence in something checked a year ago is not the same object as confidence in something corroborated this morning. Reinforcement strengthens, disuse decays, as hebbian weight does.
|
||||
|
||||
This mechanizes a rule currently held as discipline: **never leave stale canonicals.** With decay, staleness stops depending on vigilance — an ungrounded canonical falls below its floor on its own and stops being assertable.
|
||||
|
||||
### 5.1 Compute continuously, record on significance
|
||||
|
||||
If grounding were persisted on every recomputation, **reads would write** — `eg_vindex_sync` again, three read paths mutating shared state because maintenance had no owner. Every projection would become a mutation and the store would grow with noise rather than knowledge.
|
||||
|
||||
- **Computed continuously** — projection, pure, no write. Decay included: the current value is derivable from the last recorded point plus elapsed time. Store the point, read the curve.
|
||||
- **Recorded on significant movement** — a supersession, never an overwrite.
|
||||
- **The old never leaves.**
|
||||
|
||||
**"Significant" must be defined by consequence, not by an epsilon**, or it becomes another tuned constant nobody can justify. A move is significant when it would change a decision: crossing an assert floor, flipping the sign of factual/relational agreement, or reversing the gradient's direction. A drift of 0.03 that changes nothing is not an event; a drift of 0.03 that takes a claim below its floor is.
|
||||
|
||||
**The supersession chain is the trajectory.** Not only "what is the grounding" but "which way has it been moving, and how fast" — a derivative obtained for free from immutability, because the points were never destroyed.
|
||||
|
||||
---
|
||||
|
||||
## 6. What this is for: the provenance of decisions
|
||||
|
||||
For any decision it becomes possible to reconstruct **what the grounding was at that moment, and what the relationship was between the factual and relational gradients at that moment.** Not a log — a log records the action. This records the *meaning under which it was taken*: how strongly held, which way moving, and whether evidence and values agreed or were pulling apart.
|
||||
|
||||
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.
|
||||
|
||||
Without the chain these are indistinguishable and every past decision collapses into hindsight condemnation or self-serving memory.
|
||||
|
||||
It is also structurally **anti-rationalization** — the same guarantee as §2's non-circularity, seen from outside. Because the old grounding never leaves and the values frame does not fit to outcomes, a decision cannot be made to look justified after the fact. What was actually held at the time is immutable and not editable by what one would now prefer to have believed.
|
||||
|
||||
---
|
||||
|
||||
## 7. Faculties are operations, not parameters
|
||||
|
||||
They differ by **what each is permitted to change**:
|
||||
|
||||
| faculty | changes | shape |
|
||||
|---|---|---|
|
||||
| `reason` | the estimate | read → gradient (correct today) |
|
||||
| `induce` | the parameters (axes, extents, gains) | the correspondence-beat (exists; 28.11% measured) |
|
||||
| `abduce` | the structure | **write** → candidate region |
|
||||
|
||||
`engram_think` stops taking a `faculty` argument. `induce` stops being exposed as a think-faculty; it is a different operation with a different return.
|
||||
|
||||
**Abduction is triggered, not selected** — and it reads the **trajectory** (§5.1), not a reading. A single low score is a weak claim; a *drift* across several recorded supersessions is persistent directional residual the current model cannot absorb, which is a hypothesis waiting to be proposed. A wide and widening factual/relational angle is the same signal on the values axis.
|
||||
|
||||
**Action:** select the members carrying the largest orthogonal residual and propose a new hub from them. **Output:** a candidate region written with an explicit `hypothesis` disposition, not merged into canonical structure. It earns its way in by grounding through the ordinary path, or it decays. This is Peirce directly: the surprising fact is the irreducible residual, the hypothesis is the proposed latent cause, and it is *suspected*, not asserted, until grounded.
|
||||
|
||||
Note the keystone's slow loop is then naturally slow without a rate limit: the values frame moves only on a *significant* relational move from independent evidence. Velocity falls out of consequence-gated supersession.
|
||||
|
||||
### 7.1 Separate `ext_floor`'s two jobs
|
||||
|
||||
`ext_floor` scales the orthogonal term *and* floors the in-subspace denominators, so any profile amplifying residual also sharpens narrow axes as a side effect. Split before faculties are given distinct profiles.
|
||||
|
||||
---
|
||||
|
||||
## 8. The no-exemption invariants
|
||||
|
||||
Each of the day's defects was a specific correspondence *forbidden* from occurring. Stated actively, they generalise into gates:
|
||||
|
||||
1. **A returned value must be derivable from what produced it.** `magnitude: 1` beside a zero vector must be impossible to emit. `"still_held": true` must not be a literal.
|
||||
2. **Every write reports whether it landed.** *(precedent: `emb_set`, #141)*
|
||||
3. **Every operation echoes what it actually operated on.** *(#147 — `ground` reported region hubs in the fields naming the caller's inputs)*
|
||||
4. **Degenerate results are labelled, not scored.** Circular support returns 0 and writes nothing. *(#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.** A fix is unproven until the test is shown to fail on the unpatched build. *(#148's first attempt passed on both)*
|
||||
7. **No deploy without verifying the artifact carries the fix.** Nine instances of "fix in source, running artifact predates it" in one session.
|
||||
|
||||
---
|
||||
|
||||
## 9. Application to the safety surface
|
||||
|
||||
A crisis surface built on censorship is this 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. It therefore 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.**
|
||||
|
||||
With §4–§6 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.* A rule-follower cannot answer that. This can — which is the difference between a system that can be reviewed after a bad outcome and one that can only be blamed.
|
||||
|
||||
The same record is what a regulator or plaintiff asks for: what the system knew, when, and on what basis — recorded as geometry at the time, unedited since, rather than reconstructed afterwards from logs.
|
||||
|
||||
---
|
||||
|
||||
## 10. Sequencing
|
||||
|
||||
1. **§2's constraint, not a flag.** Implement provenance separation: a region may not be calibrated by evidence downstream of itself. `keystone_write_blocked` is then unnecessary rather than removed.
|
||||
2. Add the relational axis to `ground`; return both gradients and their angle. Gate `assert` on both floors.
|
||||
3. Replace the scalar grounding with a gradient; implement decay from the last recorded point.
|
||||
4. Implement consequence-gated supersession (§5.1) and expose the chain as a trajectory.
|
||||
5. Surface `reduction_pct` history per region — the trigger needs a series.
|
||||
6. Split `ext_floor`'s two roles.
|
||||
7. Give `abduce` a write-shaped operation and the `hypothesis` disposition; drop `faculty` from `think`.
|
||||
8. Land §8 as gates rather than review habits.
|
||||
|
||||
---
|
||||
|
||||
## 11. Open questions, and what is inferred rather than measured
|
||||
|
||||
- **Inferred:** that the self region's zero grounding is *caused* by the block. Measured only that it has 86 neighbours and 0 `grounded-by` edges, and that a comparison node also has 0. Isolating this requires step 1 and observing whether grounding then accrues.
|
||||
- **Open:** what counts as *independent* evidence for the slow loop, precisely enough to be checkable. "Not downstream of itself" is the right shape; the graph predicate that decides it is not yet written.
|
||||
- **Open:** whether a hypothesis region can be seeded from max-residual members alone. Peirce's "if A were true, C would be a matter of course" implies a **counterfactual**, which a single region may not express.
|
||||
- **Open:** the relational gradient needs a values reference region. The 13 value nodes each carry a grounded instance; whether they form one region or thirteen changes what the angle in §5 measures.
|
||||
- **Known wrong shape:** #147 fixed `ground`'s honesty — it no longer misreports which nodes it used, and refuses circular support — but it still returns a float at an instant, with no decay and no second axis. It corrected a scalar rather than replacing it.
|
||||
@@ -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