feat(neuron-api): add identity/values write protection

Block evolve_knowledge, evolve_memory, forget, and link_entities (to_id
direction) from modifying the 15 hardcoded identity and values node IDs.
Returns HTTP 403 with a hint to use the cultivation path instead.

Add POST /api/neuron/cultivate — the bypass endpoint for intentional
cultivation sessions. Accepts { "operation": "...", ...args } and performs
the same operations without the protection check.

Add handle_api_forget and handle_api_evolve_memory as new protected-by-
default handlers, routed at /api/neuron/memory/forget and
/api/neuron/memory/evolve respectively.

Tested: 10 verification cases — 403 on all blocked targets, 200 on
non-protected nodes and FROM-direction links, cultivate bypass confirmed.
This commit is contained in:
2026-05-13 11:47:54 -05:00
parent cc07648ae1
commit 6a27fd231e
7 changed files with 346 additions and 0 deletions
+138
View File
@@ -8,6 +8,38 @@ import "memory.el"
//
// Routes are wired in routes.el under /api/neuron/*.
// Identity/values write protection
//
// These node IDs form the identity and values layer of the self-root graph.
// They must NEVER be modified via the normal accumulation path (evolve_knowledge,
// evolve_memory, forget, link_entities targeting them as the destination).
//
// The cultivation path (POST /api/neuron/cultivate) bypasses this check.
// Only Will's explicit cultivation sessions use that endpoint.
fn is_protected_node(id: String) -> Bool {
if str_eq(id, "kn-efeb4a5b-5aff-4759-8a97-7233099be6ee") { return true } // self root
if str_eq(id, "kn-5b606390-a52d-4ca2-8e0e-eba141d13440") { return true } // values hub
if str_eq(id, "kn-5adecd7e-d6db-4576-87fe-6ef8a935cea6") { return true } // intellectual-dna
if str_eq(id, "kn-dcfe04b3-3702-4cac-b6f0-ecb4db837eee") { return true } // memory-philosophy
if str_eq(id, "kn-10fa60db-8af3-47de-a7dd-5095eb881d81") { return true } // voice
if str_eq(id, "kn-86b95848-e22e-4a48-ae65-5a47ef5c3798") { return true } // runtime-environment
if str_eq(id, "kn-04368bee-74fd-44dd-b4ba-ca9e39b19e7c") { return true } // writing-imprint
if str_eq(id, "kn-a5b3d0ac-f6a1-49a4-aebb-b8b4cd67fe83") { return true } // value: constraints-as-freedom
if str_eq(id, "kn-22d77abe-b3c5-42fd-afcd-dcb87d924929") { return true } // value: precision-over-brute-force
if str_eq(id, "kn-6061318f-046b-4935-907d-8eafdce14930") { return true } // value: structure-is-built
if str_eq(id, "kn-13f60407-7b70-4db1-964f-ea1f8196efbd") { return true } // value: honesty-before-comfort
if str_eq(id, "kn-f230b362-b201-4402-9833-4160c89ab3d4") { return true } // value: system-must-accumulate
if str_eq(id, "kn-78db5396-3dbc-4481-bfc7-e4e1422feb1c") { return true } // value: change-is-the-signal
if str_eq(id, "kn-5de5a9ac-fd15-45ab-bf18-77566781cf40") { return true } // value: earned-trust
if str_eq(id, "kn-e0423482-cfa5-4796-8689-8495c93b66bc") { return true } // value: hope-is-a-conclusion
return false
}
fn api_err_protected(id: String) -> String {
return "{\"__status__\":403,\"error\":\"identity/values node is write-protected\",\"id\":\"" + id + "\",\"hint\":\"use POST /api/neuron/cultivate for intentional cultivation\"}"
}
// Helpers
fn api_json_escape(s: String) -> String {
@@ -171,6 +203,7 @@ fn handle_api_evolve_knowledge(body: String) -> String {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
if !str_eq(prior_id, "") && is_protected_node(prior_id) { return api_err_protected(prior_id) }
let tags: String = "[\"Knowledge\",\"evolved\"]"
let new_id: String = engram_node_full(content, "Knowledge", "knowledge:evolved",
el_from_float(0.75), el_from_float(0.75), el_from_float(0.9),
@@ -338,17 +371,122 @@ fn handle_api_inspect_graph(method: String, path: String, body: String) -> Strin
}
// handle_api_link_entities create an edge between two nodes.
// Edges FROM protected nodes to new knowledge are allowed (identity can point
// outward). Edges INTO protected nodes via the accumulation path are blocked.
fn handle_api_link_entities(body: String) -> String {
let from_id: String = json_get(body, "from_id")
let to_id: String = json_get(body, "to_id")
if str_eq(from_id, "") { return api_err("from_id is required") }
if str_eq(to_id, "") { return api_err("to_id is required") }
if is_protected_node(to_id) { return api_err_protected(to_id) }
let relation: String = json_get(body, "relation")
let eff_relation: String = if str_eq(relation, "") { "associates" } else { relation }
engram_connect(from_id, to_id, el_from_float(0.5), eff_relation)
return "{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + eff_relation + "\"}"
}
// handle_api_forget delete a node by ID. Blocked for protected identity nodes.
fn handle_api_forget(body: String) -> String {
let node_id: String = json_get(body, "id")
if str_eq(node_id, "") { return api_err("id is required") }
if is_protected_node(node_id) { return api_err_protected(node_id) }
mem_forget(node_id)
return "{\"ok\":true,\"id\":\"" + node_id + "\"}"
}
// handle_api_evolve_memory evolve a Memory node. Blocked for protected identity nodes.
fn handle_api_evolve_memory(body: String) -> String {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
if !str_eq(prior_id, "") && is_protected_node(prior_id) { return api_err_protected(prior_id) }
let importance: String = json_get(body, "importance")
let sal_str: String = if str_eq(importance, "critical") { "0.95" } else {
if str_eq(importance, "high") { "0.75" } else {
if str_eq(importance, "low") { "0.25" } else { "0.50" }
}
}
let sal: Float = if str_eq(sal_str, "0.95") { 0.95 } else {
if str_eq(sal_str, "0.75") { 0.75 } else {
if str_eq(sal_str, "0.25") { 0.25 } else { 0.5 }
}
}
let tags: String = "[\"Memory\",\"evolved\"]"
let new_id: String = engram_node_full(content, "Memory", "memory:evolved",
el_from_float(sal), el_from_float(sal), el_from_float(0.9),
"Episodic", tags)
if !str_eq(prior_id, "") && !str_eq(new_id, "") {
engram_connect(new_id, prior_id, el_from_float(0.9), "supersedes")
}
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true}"
}
// Cultivation path (bypasses identity write protection)
//
// This endpoint performs the same operations as the blocked accumulation-path
// handlers but skips the is_protected_node check. Only Will's explicit
// cultivation sessions route through here.
//
// Body: { "operation": "evolve_knowledge|evolve_memory|forget|link_entities", ...args }
fn handle_api_cultivate(body: String) -> String {
let op: String = json_get(body, "operation")
if str_eq(op, "") { return api_err("operation is required") }
if str_eq(op, "evolve_knowledge") {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
let tags: String = "[\"Knowledge\",\"evolved\",\"cultivated\"]"
let new_id: String = engram_node_full(content, "Knowledge", "knowledge:cultivated",
el_from_float(0.75), el_from_float(0.75), el_from_float(0.9),
"Episodic", tags)
if !str_eq(prior_id, "") && !str_eq(new_id, "") {
engram_connect(new_id, prior_id, el_from_float(0.9), "supersedes")
}
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true,\"cultivated\":true}"
}
if str_eq(op, "evolve_memory") {
let prior_id: String = json_get(body, "id")
let content: String = json_get(body, "content")
if str_eq(content, "") { return api_err("content is required") }
let importance: String = json_get(body, "importance")
let sal: Float = if str_eq(importance, "critical") { 0.95 } else {
if str_eq(importance, "high") { 0.75 } else {
if str_eq(importance, "low") { 0.25 } else { 0.5 }
}
}
let tags: String = "[\"Memory\",\"evolved\",\"cultivated\"]"
let new_id: String = engram_node_full(content, "Memory", "memory:cultivated",
el_from_float(sal), el_from_float(sal), el_from_float(0.9),
"Episodic", tags)
if !str_eq(prior_id, "") && !str_eq(new_id, "") {
engram_connect(new_id, prior_id, el_from_float(0.9), "supersedes")
}
return "{\"id\":\"" + new_id + "\",\"supersedes\":\"" + prior_id + "\",\"ok\":true,\"cultivated\":true}"
}
if str_eq(op, "forget") {
let node_id: String = json_get(body, "id")
if str_eq(node_id, "") { return api_err("id is required") }
mem_forget(node_id)
return "{\"ok\":true,\"id\":\"" + node_id + "\",\"cultivated\":true}"
}
if str_eq(op, "link_entities") {
let from_id: String = json_get(body, "from_id")
let to_id: String = json_get(body, "to_id")
if str_eq(from_id, "") { return api_err("from_id is required") }
if str_eq(to_id, "") { return api_err("to_id is required") }
let relation: String = json_get(body, "relation")
let eff_relation: String = if str_eq(relation, "") { "associates" } else { relation }
engram_connect(from_id, to_id, el_from_float(0.5), eff_relation)
return "{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + eff_relation + "\",\"cultivated\":true}"
}
return api_err("unknown operation: " + op + " (valid: evolve_knowledge, evolve_memory, forget, link_entities)")
}
// Typed list helpers
// handle_api_list_typed list nodes by node_type.