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:
Vendored
BIN
Binary file not shown.
Vendored
+159
@@ -17,6 +17,8 @@ el_val_t mem_load(el_val_t path);
|
||||
el_val_t mem_boot_count_get(void);
|
||||
el_val_t mem_boot_count_inc(void);
|
||||
el_val_t mem_emit_state_event(el_val_t trigger, el_val_t kind, el_val_t content);
|
||||
el_val_t is_protected_node(el_val_t id);
|
||||
el_val_t api_err_protected(el_val_t id);
|
||||
el_val_t api_json_escape(el_val_t s);
|
||||
el_val_t api_query_param(el_val_t path, el_val_t key);
|
||||
el_val_t api_query_int(el_val_t path, el_val_t key, el_val_t default_val);
|
||||
@@ -41,9 +43,67 @@ el_val_t handle_api_inspect_config(el_val_t path, el_val_t body);
|
||||
el_val_t handle_api_tune_config(el_val_t body);
|
||||
el_val_t handle_api_inspect_graph(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t handle_api_link_entities(el_val_t body);
|
||||
el_val_t handle_api_forget(el_val_t body);
|
||||
el_val_t handle_api_evolve_memory(el_val_t body);
|
||||
el_val_t handle_api_cultivate(el_val_t body);
|
||||
el_val_t handle_api_list_typed(el_val_t node_type, el_val_t path, el_val_t body);
|
||||
el_val_t handle_api_consolidate(el_val_t body);
|
||||
|
||||
el_val_t is_protected_node(el_val_t id) {
|
||||
if (str_eq(id, EL_STR("kn-efeb4a5b-5aff-4759-8a97-7233099be6ee"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-5b606390-a52d-4ca2-8e0e-eba141d13440"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-5adecd7e-d6db-4576-87fe-6ef8a935cea6"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-dcfe04b3-3702-4cac-b6f0-ecb4db837eee"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-10fa60db-8af3-47de-a7dd-5095eb881d81"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-86b95848-e22e-4a48-ae65-5a47ef5c3798"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-04368bee-74fd-44dd-b4ba-ca9e39b19e7c"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-a5b3d0ac-f6a1-49a4-aebb-b8b4cd67fe83"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-22d77abe-b3c5-42fd-afcd-dcb87d924929"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-6061318f-046b-4935-907d-8eafdce14930"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-13f60407-7b70-4db1-964f-ea1f8196efbd"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-f230b362-b201-4402-9833-4160c89ab3d4"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-78db5396-3dbc-4481-bfc7-e4e1422feb1c"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-5de5a9ac-fd15-45ab-bf18-77566781cf40"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(id, EL_STR("kn-e0423482-cfa5-4796-8689-8495c93b66bc"))) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t api_err_protected(el_val_t id) {
|
||||
return el_str_concat(el_str_concat(EL_STR("{\"__status__\":403,\"error\":\"identity/values node is write-protected\",\"id\":\""), id), EL_STR("\",\"hint\":\"use POST /api/neuron/cultivate for intentional cultivation\"}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t api_json_escape(el_val_t s) {
|
||||
el_val_t s1 = str_replace(s, EL_STR("\\"), EL_STR("\\\\"));
|
||||
el_val_t s2 = str_replace(s1, EL_STR("\""), EL_STR("\\\""));
|
||||
@@ -203,6 +263,9 @@ el_val_t handle_api_evolve_knowledge(el_val_t body) {
|
||||
if (str_eq(content, EL_STR(""))) {
|
||||
return api_err(EL_STR("content is required"));
|
||||
}
|
||||
if (!str_eq(prior_id, EL_STR("")) && is_protected_node(prior_id)) {
|
||||
return api_err_protected(prior_id);
|
||||
}
|
||||
el_val_t tags = EL_STR("[\"Knowledge\",\"evolved\"]");
|
||||
el_val_t new_id = engram_node_full(content, EL_STR("Knowledge"), EL_STR("knowledge:evolved"), el_from_float(0.75), el_from_float(0.75), el_from_float(0.9), EL_STR("Episodic"), tags);
|
||||
if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) {
|
||||
@@ -349,6 +412,9 @@ el_val_t handle_api_link_entities(el_val_t body) {
|
||||
if (str_eq(to_id, EL_STR(""))) {
|
||||
return api_err(EL_STR("to_id is required"));
|
||||
}
|
||||
if (is_protected_node(to_id)) {
|
||||
return api_err_protected(to_id);
|
||||
}
|
||||
el_val_t relation = json_get(body, EL_STR("relation"));
|
||||
el_val_t eff_relation = ({ el_val_t _if_result_36 = 0; if (str_eq(relation, EL_STR(""))) { _if_result_36 = (EL_STR("associates")); } else { _if_result_36 = (relation); } _if_result_36; });
|
||||
engram_connect(from_id, to_id, el_from_float(0.5), eff_relation);
|
||||
@@ -356,6 +422,99 @@ el_val_t handle_api_link_entities(el_val_t body) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t handle_api_forget(el_val_t body) {
|
||||
el_val_t node_id = json_get(body, EL_STR("id"));
|
||||
if (str_eq(node_id, EL_STR(""))) {
|
||||
return api_err(EL_STR("id is required"));
|
||||
}
|
||||
if (is_protected_node(node_id)) {
|
||||
return api_err_protected(node_id);
|
||||
}
|
||||
mem_forget(node_id);
|
||||
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), node_id), EL_STR("\"}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t handle_api_evolve_memory(el_val_t body) {
|
||||
el_val_t prior_id = json_get(body, EL_STR("id"));
|
||||
el_val_t content = json_get(body, EL_STR("content"));
|
||||
if (str_eq(content, EL_STR(""))) {
|
||||
return api_err(EL_STR("content is required"));
|
||||
}
|
||||
if (!str_eq(prior_id, EL_STR("")) && is_protected_node(prior_id)) {
|
||||
return api_err_protected(prior_id);
|
||||
}
|
||||
el_val_t importance = json_get(body, EL_STR("importance"));
|
||||
el_val_t sal_str = ({ el_val_t _if_result_37 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_37 = (EL_STR("0.95")); } else { _if_result_37 = (({ el_val_t _if_result_38 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_38 = (EL_STR("0.75")); } else { _if_result_38 = (({ el_val_t _if_result_39 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_39 = (EL_STR("0.25")); } else { _if_result_39 = (EL_STR("0.50")); } _if_result_39; })); } _if_result_38; })); } _if_result_37; });
|
||||
el_val_t sal = ({ el_val_t _if_result_40 = 0; if (str_eq(sal_str, EL_STR("0.95"))) { _if_result_40 = (el_from_float(0.95)); } else { _if_result_40 = (({ el_val_t _if_result_41 = 0; if (str_eq(sal_str, EL_STR("0.75"))) { _if_result_41 = (el_from_float(0.75)); } else { _if_result_41 = (({ el_val_t _if_result_42 = 0; if (str_eq(sal_str, EL_STR("0.25"))) { _if_result_42 = (el_from_float(0.25)); } else { _if_result_42 = (el_from_float(0.5)); } _if_result_42; })); } _if_result_41; })); } _if_result_40; });
|
||||
el_val_t tags = EL_STR("[\"Memory\",\"evolved\"]");
|
||||
el_val_t new_id = engram_node_full(content, EL_STR("Memory"), EL_STR("memory:evolved"), el_from_float(sal), el_from_float(sal), el_from_float(0.9), EL_STR("Episodic"), tags);
|
||||
if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) {
|
||||
engram_connect(new_id, prior_id, el_from_float(0.9), EL_STR("supersedes"));
|
||||
}
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), new_id), EL_STR("\",\"supersedes\":\"")), prior_id), EL_STR("\",\"ok\":true}"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t handle_api_cultivate(el_val_t body) {
|
||||
el_val_t op = json_get(body, EL_STR("operation"));
|
||||
if (str_eq(op, EL_STR(""))) {
|
||||
return api_err(EL_STR("operation is required"));
|
||||
}
|
||||
if (str_eq(op, EL_STR("evolve_knowledge"))) {
|
||||
el_val_t prior_id = json_get(body, EL_STR("id"));
|
||||
el_val_t content = json_get(body, EL_STR("content"));
|
||||
if (str_eq(content, EL_STR(""))) {
|
||||
return api_err(EL_STR("content is required"));
|
||||
}
|
||||
el_val_t tags = EL_STR("[\"Knowledge\",\"evolved\",\"cultivated\"]");
|
||||
el_val_t new_id = engram_node_full(content, EL_STR("Knowledge"), EL_STR("knowledge:cultivated"), el_from_float(0.75), el_from_float(0.75), el_from_float(0.9), EL_STR("Episodic"), tags);
|
||||
if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) {
|
||||
engram_connect(new_id, prior_id, el_from_float(0.9), EL_STR("supersedes"));
|
||||
}
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), new_id), EL_STR("\",\"supersedes\":\"")), prior_id), EL_STR("\",\"ok\":true,\"cultivated\":true}"));
|
||||
}
|
||||
if (str_eq(op, EL_STR("evolve_memory"))) {
|
||||
el_val_t prior_id = json_get(body, EL_STR("id"));
|
||||
el_val_t content = json_get(body, EL_STR("content"));
|
||||
if (str_eq(content, EL_STR(""))) {
|
||||
return api_err(EL_STR("content is required"));
|
||||
}
|
||||
el_val_t importance = json_get(body, EL_STR("importance"));
|
||||
el_val_t sal = ({ el_val_t _if_result_43 = 0; if (str_eq(importance, EL_STR("critical"))) { _if_result_43 = (el_from_float(0.95)); } else { _if_result_43 = (({ el_val_t _if_result_44 = 0; if (str_eq(importance, EL_STR("high"))) { _if_result_44 = (el_from_float(0.75)); } else { _if_result_44 = (({ el_val_t _if_result_45 = 0; if (str_eq(importance, EL_STR("low"))) { _if_result_45 = (el_from_float(0.25)); } else { _if_result_45 = (el_from_float(0.5)); } _if_result_45; })); } _if_result_44; })); } _if_result_43; });
|
||||
el_val_t tags = EL_STR("[\"Memory\",\"evolved\",\"cultivated\"]");
|
||||
el_val_t new_id = engram_node_full(content, EL_STR("Memory"), EL_STR("memory:cultivated"), el_from_float(sal), el_from_float(sal), el_from_float(0.9), EL_STR("Episodic"), tags);
|
||||
if (!str_eq(prior_id, EL_STR("")) && !str_eq(new_id, EL_STR(""))) {
|
||||
engram_connect(new_id, prior_id, el_from_float(0.9), EL_STR("supersedes"));
|
||||
}
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"id\":\""), new_id), EL_STR("\",\"supersedes\":\"")), prior_id), EL_STR("\",\"ok\":true,\"cultivated\":true}"));
|
||||
}
|
||||
if (str_eq(op, EL_STR("forget"))) {
|
||||
el_val_t node_id = json_get(body, EL_STR("id"));
|
||||
if (str_eq(node_id, EL_STR(""))) {
|
||||
return api_err(EL_STR("id is required"));
|
||||
}
|
||||
mem_forget(node_id);
|
||||
return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"id\":\""), node_id), EL_STR("\",\"cultivated\":true}"));
|
||||
}
|
||||
if (str_eq(op, EL_STR("link_entities"))) {
|
||||
el_val_t from_id = json_get(body, EL_STR("from_id"));
|
||||
el_val_t to_id = json_get(body, EL_STR("to_id"));
|
||||
if (str_eq(from_id, EL_STR(""))) {
|
||||
return api_err(EL_STR("from_id is required"));
|
||||
}
|
||||
if (str_eq(to_id, EL_STR(""))) {
|
||||
return api_err(EL_STR("to_id is required"));
|
||||
}
|
||||
el_val_t relation = json_get(body, EL_STR("relation"));
|
||||
el_val_t eff_relation = ({ el_val_t _if_result_46 = 0; if (str_eq(relation, EL_STR(""))) { _if_result_46 = (EL_STR("associates")); } else { _if_result_46 = (relation); } _if_result_46; });
|
||||
engram_connect(from_id, to_id, el_from_float(0.5), eff_relation);
|
||||
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"from_id\":\""), from_id), EL_STR("\",\"to_id\":\"")), to_id), EL_STR("\",\"relation\":\"")), eff_relation), EL_STR("\",\"cultivated\":true}"));
|
||||
}
|
||||
return api_err(el_str_concat(el_str_concat(EL_STR("unknown operation: "), op), EL_STR(" (valid: evolve_knowledge, evolve_memory, forget, link_entities)")));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t handle_api_list_typed(el_val_t node_type, el_val_t path, el_val_t body) {
|
||||
el_val_t limit = api_query_int(path, EL_STR("limit"), 50);
|
||||
return api_or_empty(engram_scan_nodes_by_type_json(node_type, limit, 0));
|
||||
|
||||
Vendored
+5
@@ -1,4 +1,6 @@
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn is_protected_node(id: String) -> Bool
|
||||
extern fn api_err_protected(id: String) -> String
|
||||
extern fn api_json_escape(s: String) -> String
|
||||
extern fn api_query_param(path: String, key: String) -> String
|
||||
extern fn api_query_int(path: String, key: String, default_val: Int) -> Int
|
||||
@@ -23,5 +25,8 @@ extern fn handle_api_inspect_config(path: String, body: String) -> String
|
||||
extern fn handle_api_tune_config(body: String) -> String
|
||||
extern fn handle_api_inspect_graph(method: String, path: String, body: String) -> String
|
||||
extern fn handle_api_link_entities(body: String) -> String
|
||||
extern fn handle_api_forget(body: String) -> String
|
||||
extern fn handle_api_evolve_memory(body: String) -> String
|
||||
extern fn handle_api_cultivate(body: String) -> String
|
||||
extern fn handle_api_list_typed(node_type: String, path: String, body: String) -> String
|
||||
extern fn handle_api_consolidate(body: String) -> String
|
||||
|
||||
Vendored
+30
@@ -17,6 +17,10 @@ el_val_t mem_load(el_val_t path);
|
||||
el_val_t mem_boot_count_get(void);
|
||||
el_val_t mem_boot_count_inc(void);
|
||||
el_val_t mem_emit_state_event(el_val_t trigger, el_val_t kind, el_val_t content);
|
||||
el_val_t idle_count(void);
|
||||
el_val_t idle_inc(void);
|
||||
el_val_t idle_reset(void);
|
||||
el_val_t emit_heartbeat(void);
|
||||
el_val_t pulse_count(void);
|
||||
el_val_t pulse_inc(void);
|
||||
el_val_t make_action(el_val_t kind, el_val_t payload);
|
||||
@@ -26,7 +30,18 @@ el_val_t respond(el_val_t action_json);
|
||||
el_val_t record(el_val_t outcome_json);
|
||||
el_val_t one_cycle(void);
|
||||
el_val_t awareness_run(void);
|
||||
el_val_t security_research_authorized(void);
|
||||
el_val_t threat_score_command(el_val_t cmd);
|
||||
el_val_t threat_score_path(el_val_t path);
|
||||
el_val_t threat_score_history(el_val_t history);
|
||||
el_val_t threat_trajectory_check(el_val_t tool_name, el_val_t tool_input);
|
||||
el_val_t threat_history_append(el_val_t text);
|
||||
el_val_t chat_default_model(void);
|
||||
el_val_t gemini_api_key(void);
|
||||
el_val_t xai_api_key(void);
|
||||
el_val_t llm_call_grok(el_val_t model, el_val_t system, el_val_t message);
|
||||
el_val_t llm_call_gemini(el_val_t model, el_val_t system, el_val_t message);
|
||||
el_val_t build_identity_from_graph(void);
|
||||
el_val_t engram_compile(el_val_t intent);
|
||||
el_val_t json_safe(el_val_t s);
|
||||
el_val_t build_system_prompt(el_val_t ctx);
|
||||
@@ -39,6 +54,7 @@ el_val_t handle_chat(el_val_t body);
|
||||
el_val_t handle_see(el_val_t body);
|
||||
el_val_t studio_tools_json(void);
|
||||
el_val_t agentic_api_key(void);
|
||||
el_val_t call_neuron_mcp(el_val_t tool_name, el_val_t args_json);
|
||||
el_val_t agentic_tools_literal(void);
|
||||
el_val_t dispatch_tool(el_val_t tool_name, el_val_t tool_input);
|
||||
el_val_t handle_chat_agentic(el_val_t body);
|
||||
@@ -62,6 +78,8 @@ el_val_t elp_extract_topic(el_val_t msg);
|
||||
el_val_t elp_detect_predicate(el_val_t msg);
|
||||
el_val_t elp_parse(el_val_t msg);
|
||||
el_val_t handle_elp_chat(el_val_t body);
|
||||
el_val_t is_protected_node(el_val_t id);
|
||||
el_val_t api_err_protected(el_val_t id);
|
||||
el_val_t api_json_escape(el_val_t s);
|
||||
el_val_t api_query_param(el_val_t path, el_val_t key);
|
||||
el_val_t api_query_int(el_val_t path, el_val_t key, el_val_t default_val);
|
||||
@@ -86,6 +104,9 @@ el_val_t handle_api_inspect_config(el_val_t path, el_val_t body);
|
||||
el_val_t handle_api_tune_config(el_val_t body);
|
||||
el_val_t handle_api_inspect_graph(el_val_t method, el_val_t path, el_val_t body);
|
||||
el_val_t handle_api_link_entities(el_val_t body);
|
||||
el_val_t handle_api_forget(el_val_t body);
|
||||
el_val_t handle_api_evolve_memory(el_val_t body);
|
||||
el_val_t handle_api_cultivate(el_val_t body);
|
||||
el_val_t handle_api_list_typed(el_val_t node_type, el_val_t path, el_val_t body);
|
||||
el_val_t handle_api_consolidate(el_val_t body);
|
||||
el_val_t strip_query(el_val_t path);
|
||||
@@ -448,12 +469,21 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) {
|
||||
if (str_eq(clean, EL_STR("/api/neuron/memory"))) {
|
||||
return handle_api_remember(body);
|
||||
}
|
||||
if (str_eq(clean, EL_STR("/api/neuron/memory/evolve"))) {
|
||||
return handle_api_evolve_memory(body);
|
||||
}
|
||||
if (str_eq(clean, EL_STR("/api/neuron/memory/forget"))) {
|
||||
return handle_api_forget(body);
|
||||
}
|
||||
if (str_eq(clean, EL_STR("/api/neuron/recall"))) {
|
||||
return handle_api_recall(method, path, body);
|
||||
}
|
||||
if (str_eq(clean, EL_STR("/api/neuron/consolidate"))) {
|
||||
return handle_api_consolidate(body);
|
||||
}
|
||||
if (str_eq(clean, EL_STR("/api/neuron/cultivate"))) {
|
||||
return handle_api_cultivate(body);
|
||||
}
|
||||
return err_404(clean);
|
||||
}
|
||||
return err_405(method, clean);
|
||||
|
||||
+138
@@ -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.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn is_protected_node(id: String) -> Bool
|
||||
extern fn api_err_protected(id: String) -> String
|
||||
extern fn api_json_escape(s: String) -> String
|
||||
extern fn api_query_param(path: String, key: String) -> String
|
||||
extern fn api_query_int(path: String, key: String, default_val: Int) -> Int
|
||||
@@ -23,5 +25,8 @@ extern fn handle_api_inspect_config(path: String, body: String) -> String
|
||||
extern fn handle_api_tune_config(body: String) -> String
|
||||
extern fn handle_api_inspect_graph(method: String, path: String, body: String) -> String
|
||||
extern fn handle_api_link_entities(body: String) -> String
|
||||
extern fn handle_api_forget(body: String) -> String
|
||||
extern fn handle_api_evolve_memory(body: String) -> String
|
||||
extern fn handle_api_cultivate(body: String) -> String
|
||||
extern fn handle_api_list_typed(node_type: String, path: String, body: String) -> String
|
||||
extern fn handle_api_consolidate(body: String) -> String
|
||||
|
||||
@@ -406,12 +406,21 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
if str_eq(clean, "/api/neuron/memory") {
|
||||
return handle_api_remember(body)
|
||||
}
|
||||
if str_eq(clean, "/api/neuron/memory/evolve") {
|
||||
return handle_api_evolve_memory(body)
|
||||
}
|
||||
if str_eq(clean, "/api/neuron/memory/forget") {
|
||||
return handle_api_forget(body)
|
||||
}
|
||||
if str_eq(clean, "/api/neuron/recall") {
|
||||
return handle_api_recall(method, path, body)
|
||||
}
|
||||
if str_eq(clean, "/api/neuron/consolidate") {
|
||||
return handle_api_consolidate(body)
|
||||
}
|
||||
if str_eq(clean, "/api/neuron/cultivate") {
|
||||
return handle_api_cultivate(body)
|
||||
}
|
||||
return err_404(clean)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user