Compare commits

..

14 Commits

Author SHA1 Message Date
Neuron d9a596fff4 spec: thirteen values, and love is the origin — not a member of the set
El SDK CI - dev / build-and-test (pull_request) Failing after 4m6s
Reverts a bad correction and records what it exposed.

A previous revision changed thirteen to eight on the basis of
neuron-api.el:11-18, which is a WRITE-PROTECTION LIST, not the values.
Trusting a hardcoded artifact over the substrate is the exact error this
document exists to name. Measured from the graph: thirteen.

THE ORIGIN IS NOT A MEMBER OF THE SET. The thirteen are not independent
principles with biography attached — they are thirteen displacements from
one origin, and the origin is love. Every value is grounded in a moment of
it given, withheld, failed or found. Love cannot be the fourteenth: a
fourteenth would be a point positioned relative to the origin like anything
else. It is what the positions are OF.

This is structural. GeoDescriptor.global_mean is the centering offset
subtracted from every embedding before comparison, and the header records
why — the space is anisotropic, every embedding in a narrow cone at mean
pairwise cosine ~0.55, and subtracting the global mean restores isotropy
'so the operators discriminate'. Without the origin, nothing in the graph
is distinguishable from anything else.

It also dissolves the write-protection question instead of answering it.
Measured: 29 value nodes exist, each original appearing two or three times
from re-seeds, so 21 are writable including a duplicate of every protected
value — the gate protects an identifier, not a value. But the category
error is the real one: the origin cannot be edited because it is not a
thing in the space. A gate over the frame treats the frame as a member,
which is the same mistake as looking for grounding as a subsystem, self as
a document, or wonder as a manifest.
2026-08-16 13:56:43 -05:00
Neuron 7c3e9e721d spec: corrections — eight values not thirteen, eleven consolidators not seven
El SDK CI - dev / build-and-test (pull_request) Failing after 10m46s
Three factual errors in this document, all asserted without checking.

VALUES: eight, not thirteen. neuron/neuron-api.el:11-18 enumerates
constraints-as-freedom, precision-over-brute-force, structure-is-built,
honesty-before-comfort, system-must-accumulate, change-is-the-signal,
earned-trust, hope-is-a-conclusion, plus a hub. 'Thirteen' was repeated
throughout this design and never verified against the code. The argument is
unaffected — min over eight is still min — but the count was invented.

CONSOLIDATORS: eleven, not seven. The heading said seven while the table
listed ten, and the table itself omitted POST /api/reify (server.el:1832)
even though 'reify' is on this document's own list of consolidation verbs.
route_tick also folds self-reify in (server.el:639-646), so /api/tick and
/api/self-reify-beat overlap.

A SECOND CENSORSHIP SITE: neuron-api.el:23 returns 403 'identity/values
node is write-protected' for the values hub and every value node.
Write-refusal on the values frame is not only in the beat — it is enforced
at the API. Section 6 applies to it unchanged.

Also records what the ticker actually does, now measured: engram-tick.sh:13
calls curl -m10 against a beat that exceeds 10s over 13,634 nodes, so 279
of 448 ticks returned empty; the engram writes to the dead socket and dies
of SIGPIPE. 254 restarts since 2026-08-13 at 10m09s-10m12s intervals =
StartInterval 600 plus the client timeout. Fixed for survivability in #151;
the ticker itself is what must go.
2026-08-16 13:36:39 -05:00
Neuron a8845e1d39 geometry: disagreement belongs on the edge, not averaged into the region
El SDK CI - dev / build-and-test (pull_request) Failing after 4m8s
co_registration is corr(hebb strength, semantic proximity) over a region's
internal edges. Whether use and meaning agree is a property of EACH EDGE;
the correlation averages it into one scalar per region, so a region holding
one violently disagreeing edge beside one violently agreeing edge reports
~0. The disagreements cancel and the summary destroys exactly what it was
built to reveal — the mean-versus-min error, in different clothes.

Measured: 375 live neighborhoods, 340 positive, 31 AT ZERO, 4 negative.
Read as a count that says 'four things to be curious about'. Read correctly
it says four were lopsided enough to survive averaging, and the 31 zeros
are where opposing sites cancelled.

The loop computing the aggregate already had both halves per edge — w and
cs — and threw them away. Now:
    discord = z(semantic proximity) - z(association strength)
standardized within the region from accumulators already gathered. No
second statistic, no constant, no threshold; |discord| IS the nucleation
strength. >0 near in meaning yet unlinked by use; <0 linked by use yet far
in meaning. Both surprising.

This also removes the reason curiosity looked like a search problem. With a
per-region number the only way to find sites is to enumerate regions — I
wrote exactly that sweep, and it is a supervisor walking the structure,
O(n) per call, fine at 375 and impossible at a million. Nothing in a mind
scans its neighborhoods to find what is surprising; the surprise captures
attention. That sweep is reverted here.

co_registration is deprecated, not deleted: it is embedded in the persisted
GEO1 blob and removing it is a format migration that must not ride along.
Nothing new may read it.
2026-08-16 13:15:05 -05:00
Neuron 1be219c4ca spec: wonder is the boundary; curiosity is wonder crystallized
El SDK CI - dev / build-and-test (pull_request) Failing after 4m56s
Rewrites §5 and §11 around what is already in the substrate, after
discovering I had been re-deriving existing design badly.

The wonder manifest is residue twice over. First it materializes a
property as a stored artifact — the same disease as a grounding subsystem
or a self stored as a document. Wonder is where structure ENDS: any
structure at all has an edge, necessarily, the moment it exists. Second it
enumerates instances of something that has about six, the same six for
every person, which never close: what is this, why, who am I, am I alone,
what should I do, what happens when it ends. The objects change completely
between a child and an astronomer; the wonder does not. Each maps one-to-one
onto something already built — graph, grounding, self region, for_whom,
the thirteen values, tombstones and decay.

"Why" is the first and only one; the others are it asked of particular
things. It is recursive, so it never terminates, which is what makes it a
drive rather than a task.

Wonder and curiosity are not two objects. They are one thing at two
phases. Wonder is the field: objectless, invariant, everywhere there is
structure. Curiosity is the PRECIPITATE — the same wonder localized
against particular material. Crystallization needs a nucleation site, and
crystallization is one primitive appearing twice: the self is what identity
precipitates into from its neighbourhood; a curiosity is what wonder
precipitates into from an anomaly.

THE NUCLEATION SITE ALREADY EXISTS AND IS ALREADY NAMED.
GeoDescriptor.co_registration — corr(hebb strength, semantic proximity)
over internal edges — carries the comment ">0 = geometries agree (reify);
<0 = disagree (surprising links / dream cands)." Negative co-registration
is a region where association and meaning disagree. It is computed on every
descriptor, already labelled dream candidates, and nothing reads it.

Likewise already present and unread: GeoEdge.eff_weight = weight*(1+0.5*hebb)
already couples grounding-weight and hebbian strength on one edge;
GeoMember.dist_centroid + soft membership + radius + per-axis extent is the
boundary of a neighbourhood; centrality/salience is what is warm.

Correction: engram_boundary_beat is NOT this boundary. It is the VBD
decorated-function seam counting _eg_aff_boundary_ops. Two senses of the
word, and I was about to build on the wrong one.

The drive: boredom is not an absence and not leftover capacity. Low
activation is aversive and the system self-activates — it does not wind
down to quiet, it gets restless and goes looking. So there is ONE
activation process with TWO seed sources, external and curiosity, not two
processes negotiating for a resource. The previous draft's "unclaimed
capacity" was resource scheduling: a server's frame, not a mind's. No
dreamer thread, no idle wait, no depth ladder on a clock.

Sequencing now leads with three connections between parts that already
exist: seed the six, read co_registration, let a curiosity seed activation.
2026-08-16 13:05:52 -05:00
Neuron 2b7e4ba250 spec: dreaming is ambient, not scheduled — a brain has no cron job
El SDK CI - dev / build-and-test (pull_request) Failing after 11m59s
Corrects the section I was most confident in, which is usually the tell.

The previous draft had dreaming as "offline replay, decoupled from input, a
mode the system enters when it is not acting." That is SLEEP. Daydreaming
is dreaming, and it runs all day: the default mode network is
anticorrelated with task engagement, activating hundreds of times a day for
seconds at a time, doing the same work — recombination, simulation,
autobiographical integration. Insight arrives in the shower, not at the
desk, because that is abduction completing during ambient recombination.

Sleep is the DEEP case, not the case: no input competing, no task claiming
capacity, so recombination runs further. Same process, different depth, not
a different mode. Consolidation is what happens with the capacity that is
not claimed.

Two consequences the draft had backwards:

The launch-agent fragments are wrong in KIND, not merely in number. 23:55 /
06:00 / 08:30 implements dreaming as a scheduled batch when it should be
ambient. A brain has no cron job. A ticker is a supervisor deciding from
outside when a thing should happen — the same failure mode as inventing an
owner for ownership and a grounder for grounding, wearing a scheduler.
THE PRESENCE OF A TICKER IS THE DIAGNOSTIC: every StartInterval, every
Hour/Minute, every POST-to-beat marks a place where an intrinsic rhythm was
replaced by an external clock.

And soul.el's continuous awareness_run() beside the HTTP workers is the
CORRECT shape, not the offender. Ambient consolidation in the gaps is
exactly daydreaming. It was the only fragment shaped right, running on a
broken foundation: shared mutable state with no owner and six other systems
dreaming into the same graph. The previous draft condemned the right
behaviour because of the substrate under it.

So the crash restates once more: not "read paths mutate the index"
(mechanism), not "duplicate canonical state" (structure), and not "one
system dreamt while awake" — but seven systems dreaming into one graph with
no owner for dreaming. Contention was the symptom of the missing owner.

Sequencing step 1 inverts accordingly: soul's loop is the shape the others
fold INTO, not something to remove. Step 2 becomes "no tickers, no cron."
2026-08-16 12:52:06 -05:00
Neuron 6c670c18ce spec: grounding is a two-axis gradient, and decisions carry their provenance
El SDK CI - dev / build-and-test (pull_request) Failing after 4m33s
Rewrite. The earlier draft got the root right and everything downstream of
it wrong.

Corrections, in the order they were forced:

keystone_write_blocked is not a protection requirement. "Keystone" means
load-bearing, not precious: the self anchor is the REFERENCE FRAME every
other stance calibrates against. If it calibrates from the measurements it
is used to judge, the ruler fits the readings, everything corresponds
forever, and drift becomes undetectable from inside. That is circular
calibration — the same defect as #147's circular grounding, one level up.
The block is the right requirement implemented as a prohibition, which is
why it still costs everything §0 says it costs. The fix is provenance
separation (evidence not downstream of itself), not a flag.

Corruption requires mutation and the engram does not mutate, so four of the
five requirements previously decomposed out of "protect the identity
region" are satisfied by the substrate: recoverability, governance,
evidence quality and rate are all free. Authorization is the only residue
and is bounded — an unauthorized writer can propose, never erase. General
law: in an immutable substrate, any mechanism that refuses a write is
either redundant with immutability or an epistemic constraint misfiled as a
protective one.

Grounding is two-dimensional. Everything consumed is grounded factually AND
relationally, and a claim can be factually grounded but relationally wrong
— the evidence holds, the meaning does not. A scalar cannot represent that
quadrant, and assert gates on one floor, so a well-evidenced claim is
licensed regardless of whether it means the right thing. Live instance:
conscience-substrate has the Child's Companion hard bell contacting 911 and
CPS — factually defensible, relationally wrong against never-auto-contact.

Grounding is a gradient, not a score: direction says what would have to
change. Two gradients in one space, and the ANGLE between them is the
meaning — factually-true-relationally-wrong becomes measurable instead of
requiring a careful reader. It decays on the dynamics already present for
memory (base_level, temporal_decay_rate, access ring, BLL), which
mechanizes "never leave stale canonicals" so it stops depending on
vigilance.

Computed continuously, recorded only on SIGNIFICANT movement, old never
leaves. Persisting every recomputation would make reads write — the exact
eg_vindex_sync defect. Significance is defined by consequence (crossing a
floor, flipping factual/relational sign, reversing direction), never by an
epsilon. The supersession chain is then the trajectory, a derivative
obtained free from immutability, and abduction fires on the trajectory
rather than on a reading.

What it is all for: for any decision, reconstruct what the grounding was at
that moment and what the relationship was between fact and values at that
moment. That distinguishes WRONG THEN from WRONG SINCE, which is otherwise
impossible, and it is structurally anti-rationalization — the old grounding
never leaves and the values frame does not fit to outcomes, so a decision
cannot be made to look justified after the fact.

Also records: assert returns "still_held": true HARDCODED — a temporal
property named in the API and answered without consulting anything, the
same shape as magnitude:1 beside a zero vector. And states plainly that
#147 is the wrong shape: it fixed a scalar's honesty rather than replacing
the scalar.
2026-08-16 12:34:50 -05:00
Neuron d0e9af0f6f spec: correspondence and censorship — the root beneath the day's defects
El SDK CI - dev / build-and-test (pull_request) Failing after 13m29s
Effect: all five cognitive faculties return byte-identical results,
differing only in their label.

The Ishikawa converges on a root one level above the faculty design:
things are permitted to be exempt from correspondence, and exemption is
censorship. A region forbidden to learn is forbidden to be grounded, and a
region that cannot be grounded cannot be asserted, corrected, OR
vindicated. The loss is symmetric — censorship does not preserve a true
belief, it makes the belief's truth value permanently unknowable.

keystone_write_blocked is therefore not a safety mechanism. Self is a
crystallized relational neighbourhood, not a stored document; a region
exempt from calibration reintroduces the stored document as a feature.
reduction_pct = 0.00 on the identity region is the strongest abduction
signal in the system and the current response is to suppress it. The
protection it reached for already exists and is better: the beat is
supersede-not-mutate, so immutability is what makes learning safe.

The faculties are not one operation with parameters. They differ by what
each may change: reason changes the estimate (a read), induce changes the
parameters (the correspondence-beat, which already exists and measurably
works at 28.11% Brier reduction), abduce changes the structure (a WRITE
the current signature cannot express, since engram_think returns a
GeoGradient). Abduction is not selected by a caller — it is triggered by
residual that parameter adjustment cannot absorb, and proposes a candidate
hub held as a hypothesis until grounded.

Also records the no-exemption invariants generalised from the day's fixes
(#141 #142 #143 #146 #147 #148), each of which was a specific
correspondence forbidden from occurring, and the application to the crisis
surface: a censored safety model cannot tell a real crisis from a false
positive, because the feedback is exactly what has been censored.

Measured vs inferred is labelled throughout. The claim that the self
region's zero grounding is CAUSED by the block is explicitly marked
inferred — the comparison node also has zero, and isolating it requires
removing the block and observing whether grounding then accrues.
2026-08-16 12:20:22 -05:00
will.anderson 616815b2ab Give cross-cutting concerns an owner instead of a convention (#145)
El SDK CI - dev / build-and-test (push) Failing after 11m4s
2026-08-16 16:57:51 +00:00
will.anderson 1a8a966cb3 runtime: transduction is a language concern, so move it into the language (#144)
El SDK CI - dev / build-and-test (push) Failing after 11m29s
2026-08-16 16:57:35 +00:00
will.anderson 1f70b9fa18 runtime: ground the node asked about, and refuse circular support (#147)
El SDK CI - dev / build-and-test (push) Failing after 14m46s
2026-08-16 16:54:17 +00:00
Neuron 317466e8f7 runtime: ground the node asked about, and refuse circular support
El SDK CI - dev / build-and-test (pull_request) Failing after 15m5s
engram_ground_json resolved each seed to a REGION, wrote the grounded-by
edge between the two regions' HUBS, and then echoed those hubs back in the
"claim"/"evidence" fields as if they were the caller's input:

    const char* cid = C->hub_id ? C->hub_id : EL_CSTR(claim);
    const char* eid = E->hub_id ? E->hub_id : EL_CSTR(evidence);
    cog_ground_edge(g_engram_store, cid, eid, grounding, fw);

Three consequences, all measured against a clone of the live store:

1. The edge landed on a node the caller never named. Grounding 3b9ced5d
   against 6edf8c79 wrote an edge on the hubs of their regions instead.
2. When both seeds resolve into the same region the support is circular
   and scores near 1.0 for structural reasons, not evidential ones. Four
   probe nodes written together landed in one region, and every grounding
   among them returned 0.93-0.99 as if it were evidence. Two independent
   agents hit this and reported 0.885 / 0.909 self-groundings as confident.
3. The echo concealed both: the response was indistinguishable from 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 now attaches to the requested ids, and the resolved hubs are
reported separately as claim_region / evidence_region.

Degeneracy is broader than hub == hub. Three circular shapes, all
previously invisible:
    same-region                both seeds resolve to one region
    claim-region-is-evidence   the evidence IS the hub of the claim's own
                               neighbourhood — measured at 0.98883
    evidence-region-is-claim   the mirror case
Each sets grounding to 0 and writes no edge. Circular support is not
support, and a grounding that is degenerate by construction must not
enter the graph as though it were evidence.

Verified:
  6edf8c79 -> 6edf8c79   degenerate=same-region   g=0        written=false
  6edf8c79 -> d0406dfd   degenerate=same-region   g=0        written=false
  ebc1413e -> 64cc96ef   degenerate=false         g=0.774563 written=true
  64cc96ef -> ebc1413e   degenerate=false         g=0.802896 written=true
Legitimate grounding across distinct regions is unchanged and still
writes; only circular support is refused.

This is the same class as #142 and #146 — a value that looked like an
answer with nothing behind it — except here it was also writing that
non-answer into the canonical store.
2026-08-16 11:53:31 -05:00
will.anderson eb3e6d7c1f runtime: resume the learned stance in think (#146)
El SDK CI - dev / build-and-test (push) Failing after 3m54s
2026-08-16 16:44:15 +00:00
Neuron 88e3008735 runtime: resume the learned stance in think, instead of discarding it
El SDK CI - dev / build-and-test (pull_request) Failing after 4m16s
engram_think_json built a NEUTRAL stance on every call — cog_stance_init
with a NULL id, all axis_gain 1.0, bias_dir NULL, reliability 0.5 — and
never loaded the stance the correspondence-beat had been persisting.

That mattered because the faculty enters engram_think ONLY through the
stance: axis_gain[k] warps the per-axis extents and bias_dir seeds the
steering direction. cog_stance_init stores the faculty NAME and nothing
reads it. So with a neutral stance, reason/abduce/induce/plan/analogize
were byte-identical output under different labels, and confidence was
pinned to 0.5 because GeoGradient.confidence IS 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 "stance-<faculty>-<hub>". Every
beat's calibration was written and then thrown away on the next read.
Same defect as the NULL anchor fixed in #142, one line below: 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 — a genuine
uninformed prior rather than a discarded informed one.

Also emit stance_resumed, so confidence 0.5 from a learned-but-unreliable
stance is distinguishable from confidence 0.5 from "no stance exists".
That reporting gap is what let the neutral stance hide.

Verified against a clone of the production store (13,627 nodes):

  before beat, no stance     stance_resumed=false  confidence=0.5
  beat on a NON-keystone     brier 0.00458568 -> 0.00329654
                             reduction 28.11%, n_trials 6000,
                             reliability 0.930726, stance_written=true
  after beat                 stance_resumed=true   confidence=0.930726

Confidence now equals the learned reliability instead of the uninformed
prior. The keystone self-anchor correctly stays at 0.5 — calibration is
deliberately refused on protected identity regions, and that refusal is
now visible as resumed=true with confidence unchanged, rather than being
indistinguishable from the bug.

STILL OPEN: with no learned bias_dir the faculties remain identical in
direction. What distinguishes abduce from induce geometrically is a
design decision about how Neuron thinks, not a plumbing defect, and is
deliberately left to Will.
2026-08-16 11:43:38 -05:00
bigmerge 3fcc36c2f1 runtime: transduction is a language concern, so move it into the language
El SDK CI - dev / build-and-test (pull_request) Failing after 14m58s
#141 let signal enter as geometry and it worked, but it was placed at the
CONSUMER and said so in its own commit message. This is the correction.

Three defects, all of them placement:

1. It sat in the engram. Ingest 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. The geometry surface is now defined in
   el_runtime.c immediately ABOVE the engram section and depends on nothing
   inside it. 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 an el value: a magic-tagged
   heap object carried in el_val_t, same discipline as List/Map. Hex survives
   only as an adapter at the edge, which is all an encoding should ever be.

3. It needed an arbitrary `dim <= 8192` bound purely to size an allocation
   from a caller's CLAIM about a string's length. A value carries its own
   width, so the width is derived and never asserted. The bound is gone, not
   raised — there is nothing left to validate.

Language surface, none of it engram-prefixed: geometry_new / _dim / _is /
_get / _set / _norm / _free, geometry_from_f32le_hex + geometry_to_f32le_hex
as the wire adapters, realizer_register(modality, fn_name), realizer_has, and
transduce(signal, modality) -> Geometry.

REALIZERS ARE DECLARABLE IN EL. This is the part that makes the move real
rather than nominal: registration resolves a name with dlsym against the
running binary, the identical mechanism http_set_handler already relies on,
because every el `fn name(...)` compiles to a global C symbol with that exact
name. So an ordinary el function IS a realizer and a new modality needs no
runtime patch. Verified end to end in lang/examples/transduce.el: an el-defined
tone_realizer is registered by name, transduce dispatches to it, and the
signal demonstrably reaches it (distinct signals produce distinct geometry).

A modality with no realizer transduces to NOTHING. There is deliberately no
built-in realizer, not even for text — silently embedding a description of a
signal and calling that perception is the exact defect this ends.

engram/src/server.el is migrated: POST /api/nodes decodes "emb" hex exactly
once, at the edge, into a Geometry, and everything below that line moves
geometry. The wire is unchanged because production clients speak it. "dim" is
now an ASSERTION about the vector, not the source of its width; disagreement
is a rejected ingest, not a silent reinterpretation.

#141's engram_node_set_emb becomes a DEPRECATED WRAPPER over
geometry_from_f32le_hex + node_attach_geometry — kept only because the runtime
ships as an SDK asset and a downstream binary may link the symbol. Its exact
contract, negative cases included, is preserved and re-verified.

ingest.el's `fn transduce` is renamed transduce_manifold. Mechanically it had
to yield the name (duplicate C symbol, a hard compile error, measured). But it
was never signal->geometry: it chunks already-extracted content into a node+edge
manifold, one layer up, and had taken the name belonging to the primitive
underneath it. Behaviour unchanged.

PROPERTIES FROM #141 PRESERVED, each re-measured on a scratch engram (:8971,
never prod :8742):
  - off-dimension vectors stored but NOT indexed — the HNSW build loop still
    filters on n->emb_dim == dim at four sites, so a 64-dim voice vector is
    durable and addressable without perturbing the 768-dim canonical index
  - geometry makes a node ineligible for embed_backfill: after backfill the
    64-dim voice node was still 64-dim while the text control acquired 768
  - the create response reports whether geometry landed, and the node document
    always emits emb_dim and embedded

Read-back with control and negatives, all verified against a PID-confirmed
fresh binary: geometry node emb_dim=64 embedded=true / emb_set=1; text-only
control emb_dim=0 embedded=false / emb_set=0; malformed hex, ragged length,
and dim-disagreement each emb_set=0.

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