Archived
forge: install genesis seeds at their declared node ids
A persona seed is generative: values and biography are prose, the installer
mints an id for each node, and the ids are incidental because nothing
outside the graph refers to them. A genesis seed is declarative -- it ships
identity_nodes[] with explicit ids and edges[] written in terms of them,
and the ids are the artifact. is_protected_node keys on exact ids and every
declared edge references them.
install.el only understood the persona schema. It never read identity_nodes
or edges, so installing Neuron's genesis seed minted 102 unrelated nodes
and produced a graph structurally unrelated to the one the seed describes.
The result in the live graph: 87 of 102 declared nodes absent, 2 present
with only their label as content, 202 of 214 edges never laid.
Branch on seed_type. Seeds without one reach the identical persona code
they always did. Genesis seeds render their declared nodes and edges into a
snapshot-format file and hand it to the engram, which honors the ids --
POST /api/nodes/reseed where available, POST /api/load-merge otherwise.
Both skip nodes already present and dedup edges, so a rerun is a no-op by
construction.
Three things this path does differently, all inherent to what a genesis
seed is:
- It never provisions. A persona seed gets a port and a launchd Engram; a
genesis seed installs into an engram that already exists and is owned by
something else. The ":8742 refusal" is also inverted -- that guard stops
a foreign soul landing in Neuron's engram, and Neuron's own identity
graph is the one thing that belongs there. GENESIS_CONFIRM takes its
place, because rewriting the graph a running CGI reads itself out of
should never happen because someone typed a familiar command with an
unfamiliar file.
- It splices raw JSON tokens rather than decoding and re-escaping. The seed
bodies are multi-KB markdown with newlines, quotes and \u escapes; a
round-trip through str_escape_json would have to reproduce every escape
the author wrote, and any it does not know about silently corrupts the
body. Copying a token verbatim cannot corrupt what it never decodes.
- It verifies by re-reading. The bug this replaces reported success while
delivering empty bodies, so success is not what the write returned, it is
what the graph now holds: every declared id is re-read and audited
against the hollow signature, and the manifest reports created / filled /
skipped / failed with the failing ids named.
Hollow detection is deliberately conservative -- under 40% of the declared
body and under 400 bytes -- so a node whose body legitimately grew in the
graph is left alone. Genesis restores what is missing; it does not
overwrite what the graph has learned.
Also registers Neuron in registry.json (installed:false until the identity
load is authorized) and defines HAVE_CURL in CFLAGS, without which the
el-compiler runtime links fine and then answers every HTTP call with
{"error":"not built with HAVE_CURL"}.
Verified against a sandbox engram seeded to mirror the live graph's state
for this seed: created 87, filled 2, skipped 13, failed 0; 102/102 nodes
verified byte-exact against the seed where the seed is authoritative;
214/214 edges laid; 682/682 non-seed incident edges preserved through the
two in-place replaces; rerun reports 102 skipped, 0 nodes and 0 edges
added. Against an engram without the reseed route it creates the 87, fills
none, and fails loudly naming the 2 it could not repair.
This commit is contained in:
@@ -2,7 +2,12 @@ EL_ROOT ?= $(HOME)/Development/neuron-technologies/foundation/el
|
||||
EL_LIB ?= $(EL_ROOT)/el-compiler/runtime
|
||||
ELC ?= $(EL_ROOT)/dist/platform/elc
|
||||
CC ?= cc
|
||||
CFLAGS := -std=c11 -O2 -I$(EL_LIB)
|
||||
# -DHAVE_CURL: the el-compiler runtime guards its libcurl client behind this
|
||||
# macro, so a build without it links fine and then answers every http_get /
|
||||
# http_post_json with {"error":"not built with HAVE_CURL"} — forge is entirely
|
||||
# HTTP, so it fails at the first engram call. The SDK release runtime CI pulls
|
||||
# has no such guard; defining it is a no-op there.
|
||||
CFLAGS := -std=c11 -O2 -DHAVE_CURL -I$(EL_LIB)
|
||||
|
||||
.PHONY: build clean install
|
||||
|
||||
|
||||
+27
-57
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"imprints": [
|
||||
{
|
||||
{"version":"1.0","imprints":[{
|
||||
"subject": "Bobby Anderson",
|
||||
"slug": "bobby-anderson",
|
||||
"seed_file": "seeds/bobby-anderson-seed.json",
|
||||
@@ -13,8 +10,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-bobby-anderson-2026",
|
||||
"soul_daemon_url": "http://localhost:8901"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Alan Turing",
|
||||
"slug": "alan-turing",
|
||||
"seed_file": "seeds/alan-turing-seed.json",
|
||||
@@ -26,8 +22,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-alan-turing-2026",
|
||||
"soul_daemon_url": "http://localhost:8902"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Albert Einstein",
|
||||
"slug": "albert-einstein",
|
||||
"seed_file": "seeds/albert-einstein-seed.json",
|
||||
@@ -39,8 +34,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-albert-einstein-2026",
|
||||
"soul_daemon_url": "http://localhost:8903"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Nikola Tesla",
|
||||
"slug": "nikola-tesla",
|
||||
"seed_file": "seeds/nikola-tesla-seed.json",
|
||||
@@ -52,8 +46,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-nikola-tesla-2026",
|
||||
"soul_daemon_url": "http://localhost:8904"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Leonardo da Vinci",
|
||||
"slug": "leonardo-da-vinci",
|
||||
"seed_file": "seeds/leonardo-da-vinci-seed.json",
|
||||
@@ -65,8 +58,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-leonardo-da-vinci-2026",
|
||||
"soul_daemon_url": "http://localhost:8905"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Richard Feynman",
|
||||
"slug": "richard-feynman",
|
||||
"seed_file": "seeds/richard-feynman-seed.json",
|
||||
@@ -78,8 +70,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-richard-feynman-2026",
|
||||
"soul_daemon_url": "http://localhost:8906"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Carl Sagan",
|
||||
"slug": "carl-sagan",
|
||||
"seed_file": "seeds/carl-sagan-seed.json",
|
||||
@@ -91,8 +82,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-carl-sagan-2026",
|
||||
"soul_daemon_url": "http://localhost:8907"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Ren\u00e9 Descartes",
|
||||
"slug": "rene-descartes",
|
||||
"seed_file": "seeds/rene-descartes-seed.json",
|
||||
@@ -104,8 +94,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-rene-descartes-2026",
|
||||
"soul_daemon_url": "http://localhost:8908"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Robin Williams",
|
||||
"slug": "robin-williams",
|
||||
"seed_file": "seeds/robin-williams-seed.json",
|
||||
@@ -117,8 +106,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-robin-williams-2026",
|
||||
"soul_daemon_url": "http://localhost:8909"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Frederick Douglass",
|
||||
"slug": "frederick-douglass",
|
||||
"seed_file": "seeds/frederick-douglass-seed.json",
|
||||
@@ -130,8 +118,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-frederick-douglass-2026",
|
||||
"soul_daemon_url": "http://localhost:8910"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Marcus Aurelius",
|
||||
"slug": "marcus-aurelius",
|
||||
"seed_file": "seeds/marcus-aurelius-seed.json",
|
||||
@@ -143,8 +130,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-marcus-aurelius-2026",
|
||||
"soul_daemon_url": "http://localhost:8911"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Friedrich Nietzsche",
|
||||
"slug": "friedrich-nietzsche",
|
||||
"seed_file": "seeds/friedrich-nietzsche-seed.json",
|
||||
@@ -156,8 +142,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-friedrich-nietzsche-2026",
|
||||
"soul_daemon_url": "http://localhost:8912"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "James Baldwin",
|
||||
"slug": "james-baldwin",
|
||||
"seed_file": "seeds/james-baldwin-seed.json",
|
||||
@@ -169,8 +154,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-james-baldwin-2026",
|
||||
"soul_daemon_url": "http://localhost:8913"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Ada Lovelace",
|
||||
"slug": "ada-lovelace",
|
||||
"seed_file": "seeds/ada-lovelace-seed.json",
|
||||
@@ -182,8 +166,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-ada-lovelace-2026",
|
||||
"soul_daemon_url": "http://localhost:8914"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Harriet Tubman",
|
||||
"slug": "harriet-tubman",
|
||||
"seed_file": "seeds/harriet-tubman-seed.json",
|
||||
@@ -195,8 +178,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-harriet-tubman-2026",
|
||||
"soul_daemon_url": "http://localhost:8915"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Virginia Woolf",
|
||||
"slug": "virginia-woolf",
|
||||
"seed_file": "seeds/virginia-woolf-seed.json",
|
||||
@@ -208,8 +190,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-virginia-woolf-2026",
|
||||
"soul_daemon_url": "http://localhost:8916"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Hedy Lamarr",
|
||||
"slug": "hedy-lamarr",
|
||||
"seed_file": "seeds/hedy-lamarr-seed.json",
|
||||
@@ -221,8 +202,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-hedy-lamarr-2026",
|
||||
"soul_daemon_url": "http://localhost:8917"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Marie Curie",
|
||||
"slug": "marie-curie",
|
||||
"seed_file": "seeds/marie-curie-seed.json",
|
||||
@@ -234,8 +214,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-marie-curie-2026",
|
||||
"soul_daemon_url": "http://localhost:8918"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Helen Keller",
|
||||
"slug": "helen-keller",
|
||||
"seed_file": "seeds/helen-keller-seed.json",
|
||||
@@ -247,8 +226,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-helen-keller-2026",
|
||||
"soul_daemon_url": "http://localhost:8919"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Superman",
|
||||
"slug": "superman",
|
||||
"seed_file": "seeds/superman-seed.json",
|
||||
@@ -260,8 +238,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-superman-2026",
|
||||
"soul_daemon_url": "http://localhost:8920"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Stephen King",
|
||||
"slug": "stephen-king",
|
||||
"seed_file": "/Users/will/Development/neuron-technologies/forge/seeds/stephen-king-seed.json",
|
||||
@@ -273,8 +250,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-stephen-king-2026",
|
||||
"soul_daemon_url": "http://localhost:8921"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Jon Stewart",
|
||||
"slug": "jon-stewart",
|
||||
"seed_file": "/Users/will/Development/neuron-technologies/forge/seeds/jon-stewart-seed.json",
|
||||
@@ -286,8 +262,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-jon-stewart-2026",
|
||||
"soul_daemon_url": "http://localhost:8922"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Jimmy Kimmel",
|
||||
"slug": "jimmy-kimmel",
|
||||
"seed_file": "/Users/will/Development/neuron-technologies/forge/seeds/jimmy-kimmel-seed.json",
|
||||
@@ -299,8 +274,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-jimmy-kimmel-2026",
|
||||
"soul_daemon_url": "http://localhost:8923"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "John Oliver",
|
||||
"slug": "john-oliver",
|
||||
"seed_file": "/Users/will/Development/neuron-technologies/forge/seeds/john-oliver-seed.json",
|
||||
@@ -312,8 +286,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-john-oliver-2026",
|
||||
"soul_daemon_url": "http://localhost:8924"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Stephen Colbert",
|
||||
"slug": "stephen-colbert",
|
||||
"seed_file": "/Users/will/Development/neuron-technologies/forge/seeds/stephen-colbert-seed.json",
|
||||
@@ -325,8 +298,7 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-stephen-colbert-2026",
|
||||
"soul_daemon_url": "http://localhost:8925"
|
||||
},
|
||||
{
|
||||
},{
|
||||
"subject": "Carl Jung",
|
||||
"slug": "carl-jung",
|
||||
"seed_file": "seeds/carl-jung-seed.json",
|
||||
@@ -338,6 +310,4 @@
|
||||
"installed_at": "2026-05-03",
|
||||
"engram_api_key": "ntn-carl-jung-2026",
|
||||
"soul_daemon_url": "http://localhost:8926"
|
||||
}
|
||||
]
|
||||
}
|
||||
},{"subject":"Abraham Lincoln","slug":"abraham-lincoln","seed_file":"/Users/will/Development/neuron-technologies/foundation/forge/seeds/abraham-lincoln-seed.json","engram_db_path":"imprints/abraham-lincoln","engram_port":8827,"engram_url":"http://localhost:8827","engram_root_id":"93800f4c-3bbc-4b2d-8d13-dce498b55e2c","installed":true,"installed_at":"2026-05-03","engram_api_key":"ntn-abraham-lincoln-2026","soul_daemon_url":"http://localhost:8927"},{"subject":"Isaac Newton","slug":"isaac-newton","seed_file":"isaac-newton-seed.json","engram_db_path":"imprints/isaac-newton","engram_port":8828,"engram_url":"http://localhost:8828","engram_root_id":"097d86b3-d3a6-4977-8c9c-ab25a4702d72","installed":true,"installed_at":"2026-05-03","engram_api_key":"ntn-isaac-newton-2026","soul_daemon_url":"http://localhost:8928"},{"subject":"Pierre de Fermat","slug":"pierre-de-fermat","seed_file":"/Users/will/Development/neuron-technologies/forge/seeds/pierre-de-fermat-seed.json","engram_db_path":"imprints/pierre-de-fermat","engram_port":8829,"engram_url":"http://localhost:8829","engram_root_id":"c75c505a-3937-45a2-a79c-6f9aee2f8650","installed":true,"installed_at":"2026-05-03","engram_api_key":"ntn-pierre-de-fermat-2026","soul_daemon_url":"http://localhost:8929"},{"subject":"Neuron","slug":"neuron","seed_file":"/Users/will/Development/neuron-technologies/foundation/forge/seeds/neuron-genesis-seed.json","engram_db_path":"/private/tmp/claude-501/-Users-will/9da71693-1057-42a0-a4b4-98fc45859d8d/scratchpad/sbx/wt-data","engram_port":8994,"engram_url":"http://localhost:8994","engram_root_id":"kn-efeb4a5b-5aff-4759-8a97-7233099be6ee","installed":true,"installed_at":"2026-08-10","engram_api_key":"","soul_daemon_url":"http://localhost:7770","seed_type":"genesis"}]}
|
||||
+445
@@ -152,6 +152,333 @@ fn install_reasoning_pattern(target_url: String, root_id: String, pattern: Strin
|
||||
return node_id
|
||||
}
|
||||
|
||||
// ── Genesis seeds ─────────────────────────────────────────────────────────────
|
||||
//
|
||||
// A PERSONA seed is generative: it declares values/biography/relationships as
|
||||
// prose and the installer mints a node id for each one. The ids are incidental
|
||||
// — nothing outside the graph refers to them.
|
||||
//
|
||||
// A GENESIS seed is declarative. It ships identity_nodes[] with explicit ids
|
||||
// and edges[] that reference those ids, and the ids ARE the artifact:
|
||||
// is_protected_node() in neuron-api.el keys on exact ids, and every edge in the
|
||||
// seed is written in terms of them. Installing a genesis seed through the
|
||||
// persona path mints fresh ids and produces a graph that is structurally
|
||||
// unrelated to the one the seed describes — which is why 87 of Neuron's 102
|
||||
// identity nodes were simply absent.
|
||||
//
|
||||
// So the genesis path never mints. It writes the declared nodes and edges into
|
||||
// a snapshot-format file and hands that to the engram, which honors the ids:
|
||||
// POST /api/nodes/reseed — create-at-id, plus in-place replace of the ids
|
||||
// named in "replace" (edge-preserving)
|
||||
// POST /api/load-merge — create-at-id only; the fallback when the engram
|
||||
// predates the reseed route
|
||||
// Both skip nodes already present and dedup edges by (from,to,relation), so a
|
||||
// second run is a no-op by construction.
|
||||
|
||||
// genesis_ts — millisecond timestamp string for created_at/updated_at fields.
|
||||
fn genesis_ts() -> String {
|
||||
return int_to_str(unix_timestamp()) + "000"
|
||||
}
|
||||
|
||||
// genesis_tags — flatten a seed node's tags[] array plus its role into the
|
||||
// comma-separated tag string the engram stores. Matches the convention already
|
||||
// used by the resident kn- identity nodes ("tier:note,disposition:experimental").
|
||||
fn genesis_tags(node_obj: String) -> String {
|
||||
let tags_raw: String = json_get_raw(node_obj, "tags")
|
||||
let n: Int = json_array_len(tags_raw)
|
||||
let out: String = ""
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let t: String = json_array_get_string(tags_raw, i)
|
||||
if !str_eq(t, "") {
|
||||
if str_eq(out, "") {
|
||||
let out = str_escape_json(t)
|
||||
} else {
|
||||
let out = out + "," + str_escape_json(t)
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
let role: String = json_get_string(node_obj, "role")
|
||||
if !str_eq(role, "") {
|
||||
if str_eq(out, "") {
|
||||
let out = "role:" + str_escape_json(role)
|
||||
} else {
|
||||
let out = out + ",role:" + str_escape_json(role)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// genesis_node_record — render one seed node as a snapshot-format node object.
|
||||
//
|
||||
// id/content/label/node_type are spliced in as their RAW JSON tokens rather
|
||||
// than decoded and re-escaped. The seed bodies are multi-KB markdown carrying
|
||||
// newlines, quotes and \u escapes; a decode/re-encode round-trip through
|
||||
// str_escape_json would have to reproduce every escape the seed author wrote,
|
||||
// and any escape it does not know about silently corrupts the body. Copying the
|
||||
// token verbatim cannot corrupt what it never decodes.
|
||||
fn genesis_node_record(node_obj: String, ts: String) -> String {
|
||||
let id_raw: String = json_get_raw(node_obj, "id")
|
||||
let content_raw: String = json_get_raw(node_obj, "content")
|
||||
let label_raw: String = json_get_raw(node_obj, "label")
|
||||
let type_raw: String = json_get_raw(node_obj, "node_type")
|
||||
if str_eq(id_raw, "") { return "" }
|
||||
if str_eq(content_raw, "") { return "" }
|
||||
let ntype: String = if str_eq(type_raw, "") { "\"Knowledge\"" } else { type_raw }
|
||||
let nlabel: String = if str_eq(label_raw, "") { content_raw } else { label_raw }
|
||||
return "{\"id\":" + id_raw +
|
||||
",\"content\":" + content_raw +
|
||||
",\"node_type\":" + ntype +
|
||||
",\"label\":" + nlabel +
|
||||
",\"tier\":\"Semantic\"" +
|
||||
",\"tags\":\"" + genesis_tags(node_obj) + "\"" +
|
||||
",\"metadata\":\"{}\"" +
|
||||
",\"salience\":1.0,\"importance\":0.9,\"confidence\":1.0" +
|
||||
",\"temporal_decay_rate\":0,\"activation_count\":0,\"last_activated\":0" +
|
||||
",\"created_at\":" + ts + ",\"updated_at\":" + ts +
|
||||
",\"background_activation\":0,\"working_memory_weight\":0" +
|
||||
",\"suppression_count\":0,\"layer_id\":1}"
|
||||
}
|
||||
|
||||
// genesis_edge_record — render one seed edge as a snapshot-format edge object.
|
||||
// Seed edges use from/to; the snapshot format uses from_id/to_id.
|
||||
fn genesis_edge_record(edge_obj: String, slug: String, index: Int, ts: String) -> String {
|
||||
let from_raw: String = json_get_raw(edge_obj, "from")
|
||||
let to_raw: String = json_get_raw(edge_obj, "to")
|
||||
let rel_raw: String = json_get_raw(edge_obj, "relation")
|
||||
if str_eq(from_raw, "") { return "" }
|
||||
if str_eq(to_raw, "") { return "" }
|
||||
let relation: String = if str_eq(rel_raw, "") { "\"associates\"" } else { rel_raw }
|
||||
let w_raw: String = json_get_raw(edge_obj, "weight")
|
||||
let weight: String = if str_eq(w_raw, "") { "1.0" } else { w_raw }
|
||||
return "{\"id\":\"ge-" + slug + "-" + int_to_str(index) + "\"" +
|
||||
",\"from_id\":" + from_raw +
|
||||
",\"to_id\":" + to_raw +
|
||||
",\"relation\":" + relation +
|
||||
",\"metadata\":\"{}\"" +
|
||||
",\"weight\":" + weight +
|
||||
",\"confidence\":1.0" +
|
||||
",\"created_at\":" + ts + ",\"updated_at\":" + ts +
|
||||
",\"last_fired\":0,\"inhibitory\":0,\"layer_id\":1}"
|
||||
}
|
||||
|
||||
// genesis_live_content_len — decoded length of the node's content in the live
|
||||
// graph. Returns -1 when the node is absent (engram answers "{}" for a miss).
|
||||
fn genesis_live_content_len(target_url: String, id: String) -> Int {
|
||||
let resp: String = http_get(target_url + "/api/nodes/" + id)
|
||||
if str_eq(resp, "") { return -1 }
|
||||
if str_eq(str_trim(resp), "{}") { return -1 }
|
||||
let live_id: String = json_get_string(resp, "id")
|
||||
if str_eq(live_id, "") { return -1 }
|
||||
return str_len(json_get_string(resp, "content"))
|
||||
}
|
||||
|
||||
// genesis_is_hollow — the failure signature this whole path exists to fix.
|
||||
//
|
||||
// A hollow node has the right id and no body: `content` holds only the node's
|
||||
// own label. Observed at 30 bytes against a 4263-byte seed body and 22 against
|
||||
// 2590. The test is deliberately conservative — under 40% of the declared body
|
||||
// AND under 400 bytes — so a node whose body legitimately grew or was edited in
|
||||
// the graph is left alone. Genesis restores what is missing; it does not
|
||||
// overwrite what the graph has learned.
|
||||
fn genesis_is_hollow(live_len: Int, seed_len: Int) -> Bool {
|
||||
if live_len < 0 { return false }
|
||||
if seed_len < 300 { return false }
|
||||
if live_len >= 400 { return false }
|
||||
if live_len * 10 >= seed_len * 4 { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
// install_genesis — install a declarative genesis seed into target_url.
|
||||
// Returns the traversal root id, or "" if the install could not proceed.
|
||||
fn install_genesis(seed_json: String, seed_file: String, subject: String, slug: String, target_url: String, api_key: String) -> String {
|
||||
let root_id: String = json_get_string(seed_json, "traversal_root")
|
||||
if str_eq(root_id, "") {
|
||||
println("[forge] error: genesis seed has no traversal_root")
|
||||
return ""
|
||||
}
|
||||
|
||||
let nodes_raw: String = json_get_raw(seed_json, "identity_nodes")
|
||||
let edges_raw: String = json_get_raw(seed_json, "edges")
|
||||
let n_nodes: Int = json_array_len(nodes_raw)
|
||||
let n_edges: Int = json_array_len(edges_raw)
|
||||
if n_nodes == 0 {
|
||||
println("[forge] error: genesis seed declares no identity_nodes")
|
||||
return ""
|
||||
}
|
||||
|
||||
println("[forge] genesis seed: " + int_to_str(n_nodes) + " identity nodes, " + int_to_str(n_edges) + " edges")
|
||||
println("[forge] traversal root: " + root_id)
|
||||
println("[forge] engram: " + target_url)
|
||||
println("[forge] classifying declared nodes against the live graph...")
|
||||
|
||||
// ── Pass 1: classify every declared id ────────────────────────────────────
|
||||
let ts: String = genesis_ts()
|
||||
let node_parts: String = ""
|
||||
let replace_ids: String = ""
|
||||
let n_absent: Int = 0
|
||||
let n_hollow: Int = 0
|
||||
let n_intact: Int = 0
|
||||
let i: Int = 0
|
||||
while i < n_nodes {
|
||||
let node_obj: String = json_array_get(nodes_raw, i)
|
||||
let id: String = json_get_string(node_obj, "id")
|
||||
if !str_eq(id, "") {
|
||||
let seed_len: Int = str_len(json_get_string(node_obj, "content"))
|
||||
let live_len: Int = genesis_live_content_len(target_url, id)
|
||||
let hollow: Bool = genesis_is_hollow(live_len, seed_len)
|
||||
|
||||
if live_len < 0 {
|
||||
let n_absent = n_absent + 1
|
||||
} else {
|
||||
if hollow {
|
||||
let n_hollow = n_hollow + 1
|
||||
println("[forge] hollow: " + id + " (" + int_to_str(live_len) + " bytes live vs " + int_to_str(seed_len) + " declared)")
|
||||
if str_eq(replace_ids, "") {
|
||||
let replace_ids = "\"" + id + "\""
|
||||
} else {
|
||||
let replace_ids = replace_ids + ",\"" + id + "\""
|
||||
}
|
||||
} else {
|
||||
let n_intact = n_intact + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Intact nodes are still written to the merge file — the engram
|
||||
// skips ids it already holds, so including them costs nothing and
|
||||
// keeps the file a faithful copy of the seed.
|
||||
let rec: String = genesis_node_record(node_obj, ts)
|
||||
if !str_eq(rec, "") {
|
||||
if str_eq(node_parts, "") {
|
||||
let node_parts = rec
|
||||
} else {
|
||||
let node_parts = node_parts + "," + rec
|
||||
}
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
println("[forge] absent: " + int_to_str(n_absent) + " hollow: " + int_to_str(n_hollow) + " intact: " + int_to_str(n_intact))
|
||||
|
||||
// ── Pass 2: render edges ──────────────────────────────────────────────────
|
||||
let edge_parts: String = ""
|
||||
let j: Int = 0
|
||||
while j < n_edges {
|
||||
let edge_obj: String = json_array_get(edges_raw, j)
|
||||
let erec: String = genesis_edge_record(edge_obj, slug, j, ts)
|
||||
if !str_eq(erec, "") {
|
||||
if str_eq(edge_parts, "") {
|
||||
let edge_parts = erec
|
||||
} else {
|
||||
let edge_parts = edge_parts + "," + erec
|
||||
}
|
||||
}
|
||||
let j = j + 1
|
||||
}
|
||||
|
||||
// ── Pass 3: write the merge file and hand it to the engram ────────────────
|
||||
let stage_dir: String = FORGE_DIR + "/imprints/" + slug
|
||||
exec("mkdir -p " + stage_dir + " 2>/dev/null")
|
||||
let merge_path: String = stage_dir + "/.genesis-merge.json"
|
||||
let merge_doc: String = "{\"nodes\":[" + node_parts + "],\"edges\":[" + edge_parts + "]}"
|
||||
let wrote: Int = fs_write(merge_path, merge_doc)
|
||||
if wrote == 0 {
|
||||
println("[forge] error: could not stage merge file at " + merge_path)
|
||||
return ""
|
||||
}
|
||||
println("[forge] staged: " + merge_path)
|
||||
|
||||
let reseed_body: String = "{\"path\":\"" + merge_path + "\",\"replace\":[" + replace_ids + "],\"_auth\":\"" + api_key + "\"}"
|
||||
let resp: String = http_post_json(target_url + "/api/nodes/reseed", reseed_body)
|
||||
let blocked: Int = 0
|
||||
if str_contains(resp, "not found") {
|
||||
// Engram predates the reseed route. /api/load-merge still creates every
|
||||
// absent node at its declared id and lays every edge — it just cannot
|
||||
// replace a hollow body. Do the part that is safe and say so loudly
|
||||
// rather than reaching for a destructive substitute.
|
||||
println("[forge] note: engram has no /api/nodes/reseed — falling back to /api/load-merge")
|
||||
let merge_body: String = "{\"path\":\"" + merge_path + "\",\"_auth\":\"" + api_key + "\"}"
|
||||
let resp = http_post_json(target_url + "/api/load-merge", merge_body)
|
||||
let blocked = n_hollow
|
||||
}
|
||||
if str_eq(resp, "") {
|
||||
println("[forge] error: engram did not respond at " + target_url)
|
||||
return ""
|
||||
}
|
||||
if str_contains(resp, "\"error\"") {
|
||||
println("[forge] error: engram rejected the merge: " + resp)
|
||||
return ""
|
||||
}
|
||||
println("[forge] engram: " + resp)
|
||||
|
||||
// ── Pass 4: verify by re-reading every declared id ────────────────────────
|
||||
//
|
||||
// The bug this path replaces reported success while delivering empty
|
||||
// bodies, so success is not what the write call returned — it is what the
|
||||
// graph now holds. Re-read all of it and audit the content lengths.
|
||||
println("[forge] verifying...")
|
||||
let ok_nodes: Int = 0
|
||||
let failed_ids: String = ""
|
||||
let n_failed: Int = 0
|
||||
let k: Int = 0
|
||||
while k < n_nodes {
|
||||
let node_obj: String = json_array_get(nodes_raw, k)
|
||||
let id: String = json_get_string(node_obj, "id")
|
||||
if !str_eq(id, "") {
|
||||
let seed_len: Int = str_len(json_get_string(node_obj, "content"))
|
||||
let live_len: Int = genesis_live_content_len(target_url, id)
|
||||
if live_len < 0 {
|
||||
let n_failed = n_failed + 1
|
||||
if str_eq(failed_ids, "") {
|
||||
let failed_ids = id + " (absent)"
|
||||
} else {
|
||||
let failed_ids = failed_ids + ", " + id + " (absent)"
|
||||
}
|
||||
} else {
|
||||
if genesis_is_hollow(live_len, seed_len) {
|
||||
let n_failed = n_failed + 1
|
||||
if str_eq(failed_ids, "") {
|
||||
let failed_ids = id + " (hollow)"
|
||||
} else {
|
||||
let failed_ids = failed_ids + ", " + id + " (hollow)"
|
||||
}
|
||||
} else {
|
||||
let ok_nodes = ok_nodes + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
let k = k + 1
|
||||
}
|
||||
|
||||
// ── Manifest ──────────────────────────────────────────────────────────────
|
||||
let filled: Int = n_hollow - blocked
|
||||
println("")
|
||||
println("[forge] ── genesis manifest ─────────────────────────────")
|
||||
println("[forge] created: " + int_to_str(n_absent))
|
||||
println("[forge] filled: " + int_to_str(filled))
|
||||
println("[forge] skipped: " + int_to_str(n_intact))
|
||||
println("[forge] failed: " + int_to_str(n_failed))
|
||||
if blocked > 0 {
|
||||
println("[forge] blocked: " + int_to_str(blocked) + " hollow node(s) — engram lacks /api/nodes/reseed")
|
||||
}
|
||||
if !str_eq(failed_ids, "") {
|
||||
println("[forge] failing ids: " + failed_ids)
|
||||
}
|
||||
println("[forge] nodes verified: " + int_to_str(ok_nodes) + "/" + int_to_str(n_nodes))
|
||||
println("[forge] edges declared: " + int_to_str(n_edges) + " (engram dedups by from/to/relation)")
|
||||
println("[forge] ─────────────────────────────────────────────────")
|
||||
|
||||
log_event("install_genesis", subject,
|
||||
"root=" + root_id + " created=" + int_to_str(n_absent) +
|
||||
" filled=" + int_to_str(filled) + " skipped=" + int_to_str(n_intact) +
|
||||
" failed=" + int_to_str(n_failed) + " edges=" + int_to_str(n_edges))
|
||||
|
||||
if n_failed > 0 { return "" }
|
||||
return root_id
|
||||
}
|
||||
|
||||
// ── Registry helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
// find_max_port — scan registry and return the highest engram_port found.
|
||||
@@ -283,6 +610,111 @@ fn registry_update_root_id(subject: String, slug: String, root_id: String, seed_
|
||||
registry_replace_slug(slug, new_entry)
|
||||
}
|
||||
|
||||
// registry_update_genesis — record a genesis install. Same entry shape as every
|
||||
// other imprint, with three differences that are inherent to what a genesis seed
|
||||
// is: engram_root_id is the seed's declared traversal_root (not a minted id),
|
||||
// engram_db_path points at the engram's own data dir rather than an
|
||||
// imprints/<slug> directory forge provisioned, and engram_api_key is left empty
|
||||
// — the key for a pre-existing engram belongs to that engram's launchd
|
||||
// environment, not to a file in this repo.
|
||||
fn registry_update_genesis(subject: String, slug: String, root_id: String, seed_file: String, target_url: String, port: Int, db_path: String, soul_url: String, installed: Bool) -> Void {
|
||||
let inst: String = if installed { "true" } else { "false" }
|
||||
let inst_at: String = if installed { genesis_today() } else { "" }
|
||||
let new_entry: String = "{\"subject\":\"" + str_escape_json(subject) +
|
||||
"\",\"slug\":\"" + slug +
|
||||
"\",\"seed_file\":\"" + str_escape_json(seed_file) +
|
||||
"\",\"engram_db_path\":\"" + str_escape_json(db_path) +
|
||||
"\",\"engram_port\":" + int_to_str(port) +
|
||||
",\"engram_url\":\"" + target_url +
|
||||
"\",\"engram_root_id\":\"" + root_id +
|
||||
"\",\"installed\":" + inst + ",\"installed_at\":\"" + inst_at + "\"" +
|
||||
",\"engram_api_key\":\"\"" +
|
||||
",\"soul_daemon_url\":\"" + soul_url +
|
||||
"\",\"seed_type\":\"genesis\"}"
|
||||
registry_replace_slug(slug, new_entry)
|
||||
}
|
||||
|
||||
// genesis_today — installed_at stamp. The persona path hardcodes a date; this
|
||||
// derives one so a reinstall records when it actually happened.
|
||||
fn genesis_today() -> String {
|
||||
return str_trim(exec("date -u +%Y-%m-%d"))
|
||||
}
|
||||
|
||||
// install_genesis_main — resolve the target engram for a genesis seed, gate the
|
||||
// write, run the install, record it.
|
||||
//
|
||||
// Target resolution differs from the persona path on purpose. A persona seed
|
||||
// gets a port allocated and an Engram provisioned for it; a genesis seed
|
||||
// installs INTO an engram that already exists and is owned by something else
|
||||
// (Neuron's own engram is a launchd agent, ai.neuron.engram). So forge resolves
|
||||
// a target — ENGRAM_URL, else the registry entry, else the default — and never
|
||||
// provisions one. The ":8742 refusal" that guards the persona path is also
|
||||
// inverted here: that guard exists to stop a foreign soul landing in Neuron's
|
||||
// engram, and Neuron's own identity graph is the one thing that belongs there.
|
||||
//
|
||||
// In its place, GENESIS_CONFIRM. A genesis install rewrites the graph the
|
||||
// running CGI reads itself out of; it should never happen because someone typed
|
||||
// a familiar command with an unfamiliar file.
|
||||
fn install_genesis_main(seed_json: String, seed_file: String, subject: String, slug: String) -> String {
|
||||
let reg_json: String = fs_read(FORGE_DIR + "/registry.json")
|
||||
let entry: String = registry_find_by_slug(reg_json, slug)
|
||||
|
||||
let target_url: String = ENGRAM_DEFAULT_URL
|
||||
let reg_url: String = json_get_string(entry, "engram_url")
|
||||
if !str_eq(reg_url, "") { let target_url = reg_url }
|
||||
let env_url: String = env("ENGRAM_URL")
|
||||
if !str_eq(env_url, "") { let target_url = env_url }
|
||||
|
||||
let api_key: String = env("ENGRAM_API_KEY")
|
||||
if str_eq(api_key, "") { let api_key = json_get_string(entry, "engram_api_key") }
|
||||
|
||||
let colon_pos: Int = str_last_index_of(target_url, ":")
|
||||
let port: Int = 0
|
||||
if colon_pos > 0 {
|
||||
let port = str_to_int(str_slice(target_url, colon_pos + 1, str_len(target_url)))
|
||||
}
|
||||
|
||||
let db_path: String = env("ENGRAM_DATA_DIR")
|
||||
if str_eq(db_path, "") {
|
||||
let db_path = json_get_string(entry, "engram_db_path")
|
||||
}
|
||||
if str_eq(db_path, "") { let db_path = "imprints/" + slug }
|
||||
|
||||
let soul_url: String = env("SOUL_URL")
|
||||
if str_eq(soul_url, "") { let soul_url = "http://localhost:7770" }
|
||||
|
||||
println("[forge] seed_type: genesis")
|
||||
|
||||
if str_eq(env("GENESIS_CONFIRM"), "") {
|
||||
println("[forge] refusing to install a genesis seed without confirmation.")
|
||||
println("[forge] target: " + target_url)
|
||||
println("[forge] a genesis seed writes an identity graph into a resident engram at")
|
||||
println("[forge] declared node ids. Re-run with GENESIS_CONFIRM=1 once the target is")
|
||||
println("[forge] the one you mean (set ENGRAM_URL to aim at a sandbox).")
|
||||
return ""
|
||||
}
|
||||
|
||||
// Never provision — only wait for what is already meant to be there.
|
||||
let stats: String = http_get(target_url + "/stats")
|
||||
if str_eq(stats, "") {
|
||||
println("[forge] error: no engram responding at " + target_url)
|
||||
println("[forge] genesis installs into an existing engram; forge will not start one.")
|
||||
return ""
|
||||
}
|
||||
|
||||
let root_id: String = install_genesis(seed_json, seed_file, subject, slug, target_url, api_key)
|
||||
if str_eq(root_id, "") {
|
||||
println("[forge] genesis install did not complete cleanly — registry not updated")
|
||||
return ""
|
||||
}
|
||||
|
||||
registry_update_genesis(subject, slug, root_id, seed_file, target_url, port, db_path, soul_url, true)
|
||||
println("[forge] registry: recorded " + slug + " (root " + root_id + ")")
|
||||
println("")
|
||||
println("[forge] traverse from: " + target_url + "/api/neighbors/" + root_id)
|
||||
return root_id
|
||||
}
|
||||
|
||||
// ── Main ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn install_main() -> String {
|
||||
@@ -315,6 +747,19 @@ fn install_main() -> String {
|
||||
let slug: String = str_replace(slug_raw, " ", "-")
|
||||
println("[forge] slug: " + slug)
|
||||
|
||||
// ── Step 3b: Genesis seeds take a different path entirely ─────────────────
|
||||
//
|
||||
// Everything below this point is the persona pipeline: allocate a port,
|
||||
// provision a launchd Engram for a soul that does not have one, mint a root
|
||||
// node, mint a node per value/biography/relationship. None of that applies
|
||||
// to a genesis seed, which installs a declared graph into an engram that
|
||||
// already exists. Branch before any of it runs. Seeds with no seed_type are
|
||||
// persona seeds and reach the identical code they always did.
|
||||
let seed_type: String = json_get_string(seed_json, "seed_type")
|
||||
if str_eq(seed_type, "genesis") {
|
||||
return install_genesis_main(seed_json, seed_file, subject, slug)
|
||||
}
|
||||
|
||||
// ── Step 4: Resolve or create registry entry ──────────────────────────────
|
||||
let reg_json: String = fs_read(FORGE_DIR + "/registry.json")
|
||||
let target_url: String = ""
|
||||
|
||||
Reference in New Issue
Block a user