Compare commits

..

1 Commits

Author SHA1 Message Date
Neuron 866c75e5e2 fix(codegen): emit the declared cgi identity — it was searched for in a list that cannot contain it
El SDK Release / build-and-release (pull_request) Failing after 13m58s
El SDK CI - dev / build-and-test (pull_request) Failing after 10m32s
A cgi block is a top-level declaration, so codegen_streaming classifies it via
is_top_level_decl and releases it. The identity emission then searched
toplevel_exec_stmts for that same block. Declarations are excluded from that list by
construction, so the search could never succeed. A probe printed what it actually
saw for a program whose first statement is a cgi block: [Let, Expr]. It emitted
nothing, silently, with no diagnostic on any channel.

The code documented its own assumption — 'Since cgi blocks are rare and small, they
end up in toplevel_exec_stmts' — and that assumption was false.

Capture the declared values before the release and emit from them. The search is
deleted rather than repaired, so the failure mode is removed rather than relocated.

Proven discriminating (old fails, new passes):
  minimal cgi program, old   -> 0 el_cgi_init
  minimal cgi program, fixed -> el_cgi_init with all four declared values
  neuron soul, fixed         -> principal present in the compiled binary (0 before),
                                boots in 2s, interface 110 routes in / 110 out

Consequence: a binary now carries its declared identity as a compiled constant,
which is what the identity protocol requires. Whether the runtime surfaces it to
state_get("soul_principal") is unverified and separate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 13:48:27 -05:00
2 changed files with 43 additions and 116 deletions
-89
View File
@@ -272,89 +272,6 @@ fn route_load_merge(method: String, path: String, body: String) -> String {
"{\"ok\":true,\"nodes_added\":" + int_to_str(added_n) + ",\"edges_added\":" + int_to_str(added_e) + ",\"node_count\":" + int_to_str(engram_node_count()) + "}"
}
// route_reseed_nodes POST /api/nodes/reseed
// {"path": "<snapshot-format file>", "replace": ["<id>", ...],
// "preserve_edges": true, "_auth": "<key>"}
//
// ID-PRESERVING install/repair for declarative seed graphs.
//
// WHY THIS EXISTS (2026-08-10). Two write paths could put a node into the
// graph and neither can put a BODY onto an id that already exists:
// POST /api/nodes mints a fresh id via engram_node_full, and
// POST /api/load-merge honors the declared id but SKIPS anything already
// present. That is exactly right for the additive case and leaves one hole:
// a node that exists with a truncated body. Forge's genesis seed hit it
// two identity nodes (Voice, Voice Craft) sat in the graph carrying only
// their own label as content, 30 and 22 bytes against 4263 and 2590 in the
// seed. Their ids are load-bearing (is_protected_node keys on them and 214
// declared edges reference them), so "delete and recreate with a new id" is
// not a repair, it is a second break.
//
// Mechanism: engram has no in-place node update, so a replace is
// forget-then-merge. engram_forget also drops every INCIDENT EDGE for
// those two nodes that is 85 and 93 edges, almost all of them tag edges and
// accumulated hebbian associations that the seed does not declare and could
// not restore. preserve_edges (default true) therefore snapshots the graph
// before the forget and re-merges that snapshot afterwards: the replaced
// node is back by then so it is skipped, and every dropped incident edge
// returns through load_merge's (from_id,to_id,relation) dedup. The same
// re-merge is the failure path if the seed merge does not produce the
// node, the backup puts the original back. Rollback, not data loss.
//
// preserve_edges=false skips the two snapshot round-trips (cheap, lossy);
// use it only on a graph whose edges are fully declared by the seed.
// With no "replace" list this route is exactly /api/load-merge.
fn route_reseed_nodes(method: String, path: String, body: String) -> String {
let p: String = json_get_string(body, "path")
if str_eq(p, "") { return err_json("path is required") }
if str_eq(fs_read(p), "") { return err_json("file missing or empty") }
let dir_raw: String = env("ENGRAM_DATA_DIR")
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
let backup: String = dir + "/.reseed-backup.json"
let replace_raw: String = json_get_raw(body, "replace")
let n_replace: Int = json_array_len(replace_raw)
// Presence-aware: absent key means "preserve", only an explicit false opts out.
let pe_raw: String = json_get_raw(body, "preserve_edges")
let preserve: Bool = !str_eq(pe_raw, "false")
let before_n: Int = engram_node_count()
let before_e: Int = engram_edge_count()
let replaced: Int = 0
if n_replace > 0 {
if preserve { engram_save(backup) }
let i: Int = 0
while i < n_replace {
let rid: String = json_array_get_string(replace_raw, i)
if !str_eq(rid, "") {
// engram_get_node_json returns "{}" for a miss only forget
// ids that are actually resident, so a typo in the replace
// list is a no-op rather than a silent partial run.
let existing: String = engram_get_node_json(rid)
if !str_eq(existing, "{}") {
engram_forget(rid)
let replaced = replaced + 1
}
}
let i = i + 1
}
}
engram_load_merge(p)
if replaced > 0 {
if preserve { engram_load_merge(backup) }
}
let saved: Int = persist_canonical()
"{\"ok\":true,\"replaced\":" + int_to_str(replaced) +
",\"nodes_added\":" + int_to_str(engram_node_count() - before_n) +
",\"edges_added\":" + int_to_str(engram_edge_count() - before_e) +
",\"node_count\":" + int_to_str(engram_node_count()) +
",\"edge_count\":" + int_to_str(engram_edge_count()) + "}"
}
// route_emit_ise write an InternalStateEvent node from the soul daemon.
//
// Endpoint: POST /api/neuron/state-events
@@ -502,12 +419,6 @@ fn handle_request(method: String, path: String, body: String) -> String {
}
// Nodes
// Reseed must be tested before the exact "/api/nodes" match below reads
// as the general create path order is not load-bearing (the match is
// exact) but keeping them adjacent keeps them from drifting apart.
if str_eq(method, "POST") && (str_eq(clean, "/api/nodes/reseed") || str_eq(clean, "/nodes/reseed")) {
return route_reseed_nodes(method, path, body)
}
if str_eq(method, "POST") && (str_eq(clean, "/api/nodes") || str_eq(clean, "/nodes")) {
return route_create_node(method, path, body)
}
+43 -27
View File
@@ -3627,6 +3627,24 @@ fn codegen_streaming(tokens: [Any], sigs: [Map<String, Any>], source: String) ->
let pos: Int = 0
let el_main_body: [Map<String, Any>] = native_list_empty()
let toplevel_exec_stmts: [Map<String, Any>] = native_list_empty()
// CGI IDENTITY CAPTURE (2026-08-09). A cgi block is a top-level DECLARATION, so
// the classifier below correctly excludes it from toplevel_exec_stmts and calls
// el_release on it. The identity emission further down then searched
// toplevel_exec_stmts for it a list that structurally can never contain it
// found nothing, and emitted nothing, silently. Measured: that search sees only
// [Let, Expr] for a program whose first statement is a cgi block.
// Fix: copy the values out BEFORE the release (strings, so no dangling reference)
// and emit from these. No search, so the failure mode is removed rather than moved.
let cgi_have: Bool = false
let cgi_name_v: String = ""
let cgi_did_v: String = ""
let cgi_prin_v: String = ""
let cgi_net_v: String = ""
let cgi_eng_v: String = ""
let cgi_has_did: Bool = false
let cgi_has_prin: Bool = false
let cgi_has_net: Bool = false
let cgi_has_eng: Bool = false
let has_toplevel_exec: Bool = false
let stream_running: Bool = true
@@ -3737,6 +3755,20 @@ fn codegen_streaming(tokens: [Any], sigs: [Map<String, Any>], source: String) ->
if is_top_level_decl(stmt) {
// Import, TypeDef, EnumDef, CgiBlock, ServiceBlock, ExternFn
// These are no-ops in codegen (forward decls already emitted)
// except a CgiBlock, whose declared identity must survive
// this release to be emitted as a compiled constant.
if str_eq(sk, "CgiBlock") {
let cgi_have = true
let cgi_name_v = stmt["name"]
let cgi_did_v = stmt["dharma_id"]
let cgi_prin_v = stmt["principal"]
let cgi_net_v = stmt["network"]
let cgi_eng_v = stmt["engram"]
let cgi_has_did = stmt["has_dharma_id"]
let cgi_has_prin = stmt["has_principal"]
let cgi_has_net = stmt["has_network"]
let cgi_has_eng = stmt["has_engram"]
}
el_release(stmt)
} else {
if str_eq(sk, "Let") {
@@ -3815,33 +3847,17 @@ fn codegen_streaming(tokens: [Any], sigs: [Map<String, Any>], source: String) ->
let sig2 = native_list_get(sigs, si2)
let sk3: String = sig2["kind"]
if str_eq(sk3, "cgi_block") {
// We need the full cgi_block data it was parsed by scan_fn_sigs
// but scan only stored the name. For cgi_init we need dharma_id etc.
// Since cgi blocks are rare and small, they end up in toplevel_exec_stmts.
// Find the CgiBlock in toplevel_exec_stmts.
let tes_n: Int = native_list_len(toplevel_exec_stmts)
let tes_i: Int = 0
while tes_i < tes_n {
let tes = native_list_get(toplevel_exec_stmts, tes_i)
let tes_k: String = tes["stmt"]
if str_eq(tes_k, "CgiBlock") {
let cname2: String = tes["name"]
let cdid2: String = tes["dharma_id"]
let cprin2: String = tes["principal"]
let cnet2: String = tes["network"]
let ceng2: String = tes["engram"]
let has_did2: Bool = tes["has_dharma_id"]
let has_prin2: Bool = tes["has_principal"]
let has_net2: Bool = tes["has_network"]
let has_eng2: Bool = tes["has_engram"]
let arg_name2: String = "EL_STR(" + c_str_lit(cname2) + ")"
let arg_did2: String = cgi_arg(cdid2, has_did2)
let arg_prin2: String = cgi_arg(cprin2, has_prin2)
let arg_net2: String = cgi_arg(cnet2, has_net2)
let arg_eng2: String = cgi_arg(ceng2, has_eng2)
emit_line(" el_cgi_init(" + arg_name2 + ", " + arg_did2 + ", " + arg_prin2 + ", " + arg_net2 + ", " + arg_eng2 + ");")
}
let tes_i = tes_i + 1
// Emit from the values captured before the declaration was released.
// The previous implementation searched toplevel_exec_stmts, which by
// construction never contains a declaration so it emitted nothing and
// said nothing. See the capture block near toplevel_exec_stmts init.
if cgi_have {
let arg_name2: String = "EL_STR(" + c_str_lit(cgi_name_v) + ")"
let arg_did2: String = cgi_arg(cgi_did_v, cgi_has_did)
let arg_prin2: String = cgi_arg(cgi_prin_v, cgi_has_prin)
let arg_net2: String = cgi_arg(cgi_net_v, cgi_has_net)
let arg_eng2: String = cgi_arg(cgi_eng_v, cgi_has_eng)
emit_line(" el_cgi_init(" + arg_name2 + ", " + arg_did2 + ", " + arg_prin2 + ", " + arg_net2 + ", " + arg_eng2 + ");")
}
}
let si2 = si2 + 1