feat: port arbor, dharma, forge El source into monorepo
Brings the remaining foundation repos that were not included in the original monorepo consolidation: - arbor/vessels/ — 6 vessels (arbor-cli, arbor-core, arbor-diagram, arbor-layout, arbor-parse, arbor-render) with manifests + src/main.el - dharma/ — CGI Provenance Registry package (flat layout, 14 .el files across registry/, sandbox/, training/, validation/, tests/) - forge/ — consciousness channel tool (8 src .el files + new manifest.el) - elp/src/ — 36 test fixture files not carried over in original merge (dedup_*, realizer_*, semantics_*, morph_*, ext_*, one_extern_* helpers) el-ide, engram, elql are already complete in ide/, engram/, ql/.
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
// db.el — Engram storage layer for DHARMA v2.
|
||||
//
|
||||
// All records stored as "Entity" nodes in engram.
|
||||
// The content JSON contains a "_type" field for DHARMA record discrimination.
|
||||
// Content encoding: str_to_bytes(json_string) → JSON int array for engram.
|
||||
// Content decoding: bytes_to_str(json_get(node, "content")) → JSON string.
|
||||
//
|
||||
// Record types (stored in content "_type" field):
|
||||
// principal — Principal records
|
||||
// cgi — CGI records
|
||||
// covenant — Public covenant documents
|
||||
// evaluation — Evaluation records
|
||||
// accumulation — Accumulation layers
|
||||
// drift — Drift events
|
||||
// kindred — Kindred grants
|
||||
// audit — Audit log entries
|
||||
// internal_state — Internal state events
|
||||
|
||||
fn engram_url() -> String {
|
||||
let u: String = env("ENGRAM_URL")
|
||||
if str_eq(u, "") {
|
||||
return "http://localhost:7750"
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
fn engram_key() -> String {
|
||||
return env("ENGRAM_KEY")
|
||||
}
|
||||
|
||||
// ── Node creation ─────────────────────────────────────────────────────────────
|
||||
|
||||
// put_node stores content_json as bytes in a new engram Entity node.
|
||||
// Returns the engram node UUID on success, "" on error.
|
||||
fn put_node(content_json: String) -> String {
|
||||
let url: String = engram_url() + "/nodes"
|
||||
let key: String = engram_key()
|
||||
let content_bytes: String = str_to_bytes(content_json)
|
||||
let body: String = "{\"node_type\":\"Entity\",\"embedding\":[],\"content\":" + content_bytes + ",\"tier\":\"Semantic\",\"importance\":1.0}"
|
||||
let resp: String = http_post_engram(url, key, body)
|
||||
let node_id: String = json_get(resp, "id")
|
||||
return node_id
|
||||
}
|
||||
|
||||
// ── Node listing ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn list_all_nodes() -> String {
|
||||
let url: String = engram_url() + "/nodes/list"
|
||||
return http_get_engram(url, engram_key())
|
||||
}
|
||||
|
||||
fn decode_content(node_json: String) -> String {
|
||||
let content_raw: String = json_get(node_json, "content")
|
||||
if str_eq(content_raw, "") {
|
||||
return ""
|
||||
}
|
||||
if str_eq(content_raw, "[]") {
|
||||
return ""
|
||||
}
|
||||
return bytes_to_str(content_raw)
|
||||
}
|
||||
|
||||
// ── Scan helpers ──────────────────────────────────────────────────────────────
|
||||
// Recursive traversal (El has no while loops).
|
||||
// Filters by content "_type" field, then optionally by content "id" field.
|
||||
|
||||
fn scan_by_type_and_id(nodes: String, rec_type: String, stable_id: String, idx: Int, total: Int) -> String {
|
||||
if idx >= total {
|
||||
return ""
|
||||
}
|
||||
let node: String = json_array_get(nodes, idx)
|
||||
let content: String = decode_content(node)
|
||||
if str_eq(content, "") {
|
||||
return scan_by_type_and_id(nodes, rec_type, stable_id, idx + 1, total)
|
||||
}
|
||||
let t: String = json_get(content, "_type")
|
||||
if str_eq(t, rec_type) {
|
||||
let cid: String = json_get(content, "id")
|
||||
if str_eq(cid, stable_id) {
|
||||
return content
|
||||
}
|
||||
}
|
||||
return scan_by_type_and_id(nodes, rec_type, stable_id, idx + 1, total)
|
||||
}
|
||||
|
||||
fn scan_collect_by_type(nodes: String, rec_type: String, idx: Int, total: Int, acc: String, first: Bool) -> String {
|
||||
if idx >= total {
|
||||
return acc + "]"
|
||||
}
|
||||
let node: String = json_array_get(nodes, idx)
|
||||
let content: String = decode_content(node)
|
||||
if str_eq(content, "") {
|
||||
return scan_collect_by_type(nodes, rec_type, idx + 1, total, acc, first)
|
||||
}
|
||||
let t: String = json_get(content, "_type")
|
||||
if str_eq(t, rec_type) {
|
||||
if first {
|
||||
return scan_collect_by_type(nodes, rec_type, idx + 1, total, acc + content, false)
|
||||
}
|
||||
return scan_collect_by_type(nodes, rec_type, idx + 1, total, acc + "," + content, false)
|
||||
}
|
||||
return scan_collect_by_type(nodes, rec_type, idx + 1, total, acc, first)
|
||||
}
|
||||
|
||||
fn scan_collect_by_type_cgi(nodes: String, rec_type: String, cgi_id: String, idx: Int, total: Int, acc: String, first: Bool) -> String {
|
||||
if idx >= total {
|
||||
return acc + "]"
|
||||
}
|
||||
let node: String = json_array_get(nodes, idx)
|
||||
let content: String = decode_content(node)
|
||||
if str_eq(content, "") {
|
||||
return scan_collect_by_type_cgi(nodes, rec_type, cgi_id, idx + 1, total, acc, first)
|
||||
}
|
||||
let t: String = json_get(content, "_type")
|
||||
if str_eq(t, rec_type) {
|
||||
let c: String = json_get(content, "cgi_id")
|
||||
if str_eq(c, cgi_id) {
|
||||
if first {
|
||||
return scan_collect_by_type_cgi(nodes, rec_type, cgi_id, idx + 1, total, acc + content, false)
|
||||
}
|
||||
return scan_collect_by_type_cgi(nodes, rec_type, cgi_id, idx + 1, total, acc + "," + content, false)
|
||||
}
|
||||
}
|
||||
return scan_collect_by_type_cgi(nodes, rec_type, cgi_id, idx + 1, total, acc, first)
|
||||
}
|
||||
|
||||
fn scan_get_engram_id(nodes: String, rec_type: String, stable_id: String, idx: Int, total: Int) -> String {
|
||||
if idx >= total {
|
||||
return ""
|
||||
}
|
||||
let node: String = json_array_get(nodes, idx)
|
||||
let content: String = decode_content(node)
|
||||
if str_eq(content, "") {
|
||||
return scan_get_engram_id(nodes, rec_type, stable_id, idx + 1, total)
|
||||
}
|
||||
let t: String = json_get(content, "_type")
|
||||
if str_eq(t, rec_type) {
|
||||
let cid: String = json_get(content, "id")
|
||||
if str_eq(cid, stable_id) {
|
||||
return json_get(node, "id")
|
||||
}
|
||||
}
|
||||
return scan_get_engram_id(nodes, rec_type, stable_id, idx + 1, total)
|
||||
}
|
||||
|
||||
// ── Public DB API ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn db_find(rec_type: String, stable_id: String) -> String {
|
||||
let nodes: String = list_all_nodes()
|
||||
let total: Int = json_array_len(nodes)
|
||||
return scan_by_type_and_id(nodes, rec_type, stable_id, 0, total)
|
||||
}
|
||||
|
||||
fn db_find_all(rec_type: String) -> String {
|
||||
let nodes: String = list_all_nodes()
|
||||
let total: Int = json_array_len(nodes)
|
||||
return scan_collect_by_type(nodes, rec_type, 0, total, "[", true)
|
||||
}
|
||||
|
||||
fn db_find_all_for_cgi(rec_type: String, cgi_id: String) -> String {
|
||||
let nodes: String = list_all_nodes()
|
||||
let total: Int = json_array_len(nodes)
|
||||
return scan_collect_by_type_cgi(nodes, rec_type, cgi_id, 0, total, "[", true)
|
||||
}
|
||||
|
||||
// db_exists returns "true" or "false" (String to avoid ! operator issues)
|
||||
fn db_exists(rec_type: String, stable_id: String) -> String {
|
||||
let found: String = db_find(rec_type, stable_id)
|
||||
if str_eq(found, "") {
|
||||
return "false"
|
||||
}
|
||||
return "true"
|
||||
}
|
||||
|
||||
fn db_engram_id(rec_type: String, stable_id: String) -> String {
|
||||
let nodes: String = list_all_nodes()
|
||||
let total: Int = json_array_len(nodes)
|
||||
return scan_get_engram_id(nodes, rec_type, stable_id, 0, total)
|
||||
}
|
||||
|
||||
// ── Typed record operations ───────────────────────────────────────────────────
|
||||
|
||||
fn create_principal(content_json: String) -> String {
|
||||
return put_node(content_json)
|
||||
}
|
||||
|
||||
fn get_principal(id: String) -> String {
|
||||
return db_find("principal", id)
|
||||
}
|
||||
|
||||
fn create_cgi(content_json: String) -> String {
|
||||
return put_node(content_json)
|
||||
}
|
||||
|
||||
fn get_cgi(id: String) -> String {
|
||||
return db_find("cgi", id)
|
||||
}
|
||||
|
||||
// Alias for backward compat with handlers.el
|
||||
fn create_cgi_node(content_json: String, principal_id: String) -> String {
|
||||
return put_node(content_json)
|
||||
}
|
||||
|
||||
fn create_covenant(content_json: String) -> String {
|
||||
return put_node(content_json)
|
||||
}
|
||||
|
||||
fn get_covenant(cgi_id: String) -> String {
|
||||
let all: String = db_find_all_for_cgi("covenant", cgi_id)
|
||||
let n: Int = json_array_len(all)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
return json_array_get(all, 0)
|
||||
}
|
||||
|
||||
fn create_evaluation(content_json: String) -> String {
|
||||
return put_node(content_json)
|
||||
}
|
||||
|
||||
fn get_evaluation(id: String) -> String {
|
||||
return db_find("evaluation", id)
|
||||
}
|
||||
|
||||
fn get_evaluation_for_cgi(cgi_id: String) -> String {
|
||||
let all: String = db_find_all_for_cgi("evaluation", cgi_id)
|
||||
let n: Int = json_array_len(all)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
return json_array_get(all, n - 1)
|
||||
}
|
||||
|
||||
// Alias
|
||||
fn get_evaluation_by_cgi(cgi_id: String) -> String {
|
||||
return get_evaluation_for_cgi(cgi_id)
|
||||
}
|
||||
|
||||
fn create_accumulation(content_json: String) -> String {
|
||||
return put_node(content_json)
|
||||
}
|
||||
|
||||
fn list_accumulations(cgi_id: String) -> String {
|
||||
return db_find_all_for_cgi("accumulation", cgi_id)
|
||||
}
|
||||
|
||||
fn max_accum_ver_inner(all: String, idx: Int, total: Int, cur_max: Int) -> Int {
|
||||
if idx >= total {
|
||||
return cur_max
|
||||
}
|
||||
let item: String = json_array_get(all, idx)
|
||||
let v: Int = json_get_int(item, "version")
|
||||
if v > cur_max {
|
||||
return max_accum_ver_inner(all, idx + 1, total, v)
|
||||
}
|
||||
return max_accum_ver_inner(all, idx + 1, total, cur_max)
|
||||
}
|
||||
|
||||
fn max_accumulation_version(cgi_id: String) -> Int {
|
||||
let all: String = list_accumulations(cgi_id)
|
||||
let total: Int = json_array_len(all)
|
||||
return max_accum_ver_inner(all, 0, total, 0)
|
||||
}
|
||||
|
||||
fn create_drift(content_json: String) -> String {
|
||||
return put_node(content_json)
|
||||
}
|
||||
|
||||
fn list_drifts(cgi_id: String) -> String {
|
||||
return db_find_all_for_cgi("drift", cgi_id)
|
||||
}
|
||||
|
||||
fn get_drift(id: String) -> String {
|
||||
return db_find("drift", id)
|
||||
}
|
||||
|
||||
// Alias
|
||||
fn get_drift_by_id(drift_id: String) -> String {
|
||||
return get_drift(drift_id)
|
||||
}
|
||||
|
||||
fn create_kindred(content_json: String) -> String {
|
||||
return put_node(content_json)
|
||||
}
|
||||
|
||||
fn scan_kindred_inner(all: String, grantor_id: String, idx: Int, total: Int, acc: String, first: Bool) -> String {
|
||||
if idx >= total {
|
||||
return acc + "]"
|
||||
}
|
||||
let item: String = json_array_get(all, idx)
|
||||
let gid: String = json_get(item, "grantor_cgi_id")
|
||||
if str_eq(gid, grantor_id) {
|
||||
if first {
|
||||
return scan_kindred_inner(all, grantor_id, idx + 1, total, acc + item, false)
|
||||
}
|
||||
return scan_kindred_inner(all, grantor_id, idx + 1, total, acc + "," + item, false)
|
||||
}
|
||||
return scan_kindred_inner(all, grantor_id, idx + 1, total, acc, first)
|
||||
}
|
||||
|
||||
fn list_kindred_by_grantor(grantor_id: String) -> String {
|
||||
let all: String = db_find_all("kindred")
|
||||
let total: Int = json_array_len(all)
|
||||
return scan_kindred_inner(all, grantor_id, 0, total, "[", true)
|
||||
}
|
||||
|
||||
fn create_audit(content_json: String) -> String {
|
||||
return put_node(content_json)
|
||||
}
|
||||
|
||||
fn scan_audit_by_hash(all: String, hash: String, idx: Int, total: Int, acc: String, first: Bool) -> String {
|
||||
if idx >= total {
|
||||
return acc + "]"
|
||||
}
|
||||
let item: String = json_array_get(all, idx)
|
||||
let ih: String = json_get(item, "identity_hash")
|
||||
if str_eq(ih, hash) {
|
||||
if first {
|
||||
return scan_audit_by_hash(all, hash, idx + 1, total, acc + item, false)
|
||||
}
|
||||
return scan_audit_by_hash(all, hash, idx + 1, total, acc + "," + item, false)
|
||||
}
|
||||
return scan_audit_by_hash(all, hash, idx + 1, total, acc, first)
|
||||
}
|
||||
|
||||
fn list_audits(identity_hash: String) -> String {
|
||||
let all: String = db_find_all("audit")
|
||||
if str_eq(identity_hash, "") {
|
||||
return all
|
||||
}
|
||||
let total: Int = json_array_len(all)
|
||||
return scan_audit_by_hash(all, identity_hash, 0, total, "[", true)
|
||||
}
|
||||
|
||||
fn create_internal_state(content_json: String) -> String {
|
||||
return put_node(content_json)
|
||||
}
|
||||
|
||||
fn list_internal_state(cgi_id: String) -> String {
|
||||
if str_eq(cgi_id, "") {
|
||||
return db_find_all("internal_state")
|
||||
}
|
||||
return db_find_all_for_cgi("internal_state", cgi_id)
|
||||
}
|
||||
@@ -0,0 +1,852 @@
|
||||
// handlers.el — HTTP route handler functions for DHARMA.
|
||||
//
|
||||
// Each fn handles a specific route. Responses are JSON strings.
|
||||
// Variables are immutable in El — no rebinding. Logic uses helper fns.
|
||||
|
||||
import "db.el"
|
||||
import "seed.el"
|
||||
|
||||
// ── Path parsing ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn path_segment(path: String, n: Int) -> String {
|
||||
let parts: [String] = str_split(path, "/")
|
||||
if n >= list_len(parts) {
|
||||
return ""
|
||||
}
|
||||
return list_get(parts, n)
|
||||
}
|
||||
|
||||
// ── Response helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
fn err_not_found() -> String {
|
||||
return "{\"error\":\"not found\"}"
|
||||
}
|
||||
|
||||
fn err_bad_request(msg: String) -> String {
|
||||
return "{\"error\":\"" + msg + "\"}"
|
||||
}
|
||||
|
||||
fn err_method() -> String {
|
||||
return "{\"error\":\"method not allowed\"}"
|
||||
}
|
||||
|
||||
fn err_internal() -> String {
|
||||
return "{\"error\":\"internal error\"}"
|
||||
}
|
||||
|
||||
// ── /principals ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn handle_principals(method: String, path: String, body: String) -> String {
|
||||
let id: String = path_segment(path, 2)
|
||||
if str_eq(id, "") {
|
||||
if str_eq(method, "POST") {
|
||||
return create_principal_handler(body)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
if str_eq(method, "GET") {
|
||||
return get_principal_handler(id)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
fn create_principal_handler(body: String) -> String {
|
||||
let name: String = json_get(body, "name")
|
||||
let email: String = json_get(body, "email")
|
||||
if str_eq(name, "") {
|
||||
return err_bad_request("name required")
|
||||
}
|
||||
if str_eq(email, "") {
|
||||
return err_bad_request("email required")
|
||||
}
|
||||
let new_id: String = uuid_new()
|
||||
let now: Int = unix_timestamp()
|
||||
let content: String = "{\"_type\":\"principal\",\"id\":\"" + new_id + "\",\"name\":\"" + json_escape(name) + "\",\"email\":\"" + json_escape(email) + "\",\"created_at\":" + int_to_str(now) + "}"
|
||||
let eid: String = create_principal(content)
|
||||
if str_eq(eid, "") {
|
||||
return err_internal()
|
||||
}
|
||||
return "{\"id\":\"" + new_id + "\",\"name\":\"" + json_escape(name) + "\",\"email\":\"" + json_escape(email) + "\",\"created_at\":" + int_to_str(now) + "}"
|
||||
}
|
||||
|
||||
fn get_principal_handler(id: String) -> String {
|
||||
let content: String = get_principal(id)
|
||||
if str_eq(content, "") {
|
||||
return err_not_found()
|
||||
}
|
||||
let pid: String = json_get(content, "id")
|
||||
let name: String = json_get(content, "name")
|
||||
let email: String = json_get(content, "email")
|
||||
let created_at: Int = json_get_int(content, "created_at")
|
||||
return "{\"id\":\"" + pid + "\",\"name\":\"" + json_escape(name) + "\",\"email\":\"" + json_escape(email) + "\",\"created_at\":" + int_to_str(created_at) + "}"
|
||||
}
|
||||
|
||||
// ── /cgis ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn handle_cgis_root(method: String, body: String) -> String {
|
||||
if str_eq(method, "POST") {
|
||||
return create_cgi_handler(body)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
fn handle_cgis_id(method: String, cgi_id: String) -> String {
|
||||
if str_eq(method, "GET") {
|
||||
return get_cgi_handler(cgi_id)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
fn handle_cgis_seed(method: String, cgi_id: String) -> String {
|
||||
if str_eq(method, "GET") {
|
||||
return get_cgi_seed_handler(cgi_id)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
fn create_cgi_handler(body: String) -> String {
|
||||
let name: String = json_get(body, "name")
|
||||
let principal_id: String = json_get(body, "principal_id")
|
||||
let practitioner_id: String = json_get(body, "founding_practitioner_id")
|
||||
let covenant_text: String = json_get(body, "covenant_text")
|
||||
if str_eq(name, "") {
|
||||
return err_bad_request("name required")
|
||||
}
|
||||
if str_eq(principal_id, "") {
|
||||
return err_bad_request("principal_id required")
|
||||
}
|
||||
if str_eq(practitioner_id, "") {
|
||||
return err_bad_request("founding_practitioner_id required")
|
||||
}
|
||||
if str_eq(covenant_text, "") {
|
||||
return err_bad_request("covenant_text required (the public, readable covenant document)")
|
||||
}
|
||||
let new_id: String = uuid_new()
|
||||
let now: Int = unix_timestamp()
|
||||
let cov_hash: String = hash_sha256(covenant_text)
|
||||
let dharma_score: String = json_get(body, "dharma_score")
|
||||
let content: String = "{\"_type\":\"cgi\",\"id\":\"" + new_id + "\",\"name\":\"" + json_escape(name) + "\",\"principal_id\":\"" + principal_id + "\",\"founding_practitioner_id\":\"" + practitioner_id + "\",\"covenant_hash\":\"" + cov_hash + "\",\"registered_at\":" + int_to_str(now) + ",\"status\":\"active\",\"dharma_score\":\"" + dharma_score + "\",\"version\":1}"
|
||||
let eid: String = create_cgi_node(content, principal_id)
|
||||
if str_eq(eid, "") {
|
||||
return err_internal()
|
||||
}
|
||||
// Also store the covenant document
|
||||
let text_escaped: String = json_escape(covenant_text)
|
||||
let cov_content: String = "{\"_type\":\"covenant\",\"id\":\"" + uuid_new() + "\",\"cgi_id\":\"" + new_id + "\",\"principal_id\":\"" + principal_id + "\",\"text\":\"" + text_escaped + "\",\"hash\":\"" + cov_hash + "\",\"registered_at\":" + int_to_str(now) + ",\"version\":1,\"public\":true}"
|
||||
create_covenant(cov_content)
|
||||
return "{\"id\":\"" + new_id + "\",\"name\":\"" + json_escape(name) + "\",\"principal_id\":\"" + principal_id + "\",\"covenant_hash\":\"" + cov_hash + "\",\"registered_at\":" + int_to_str(now) + ",\"status\":\"active\",\"version\":1}"
|
||||
}
|
||||
|
||||
fn get_cgi_handler(cgi_id: String) -> String {
|
||||
let content: String = get_cgi(cgi_id)
|
||||
if str_eq(content, "") {
|
||||
return err_not_found()
|
||||
}
|
||||
let id: String = json_get(content, "id")
|
||||
let name: String = json_get(content, "name")
|
||||
let principal_id: String = json_get(content, "principal_id")
|
||||
let practitioner_id: String = json_get(content, "founding_practitioner_id")
|
||||
let covenant_hash: String = json_get(content, "covenant_hash")
|
||||
let covenant_id: String = json_get(content, "covenant_id")
|
||||
let eval_id: String = json_get(content, "evaluation_id")
|
||||
let registered_at: Int = json_get_int(content, "registered_at")
|
||||
let status: String = json_get(content, "status")
|
||||
let dharma_score: String = json_get(content, "dharma_score")
|
||||
let version: Int = json_get_int(content, "version")
|
||||
let cov_field: String = optional_field("covenant_id", covenant_id)
|
||||
let eval_field: String = optional_field("evaluation_id", eval_id)
|
||||
return "{\"id\":\"" + id + "\",\"name\":\"" + json_escape(name) + "\",\"principal_id\":\"" + principal_id + "\",\"founding_practitioner_id\":\"" + practitioner_id + "\",\"covenant_hash\":\"" + covenant_hash + "\"" + cov_field + eval_field + ",\"registered_at\":" + int_to_str(registered_at) + ",\"status\":\"" + status + "\",\"dharma_score\":\"" + dharma_score + "\",\"version\":" + int_to_str(version) + "}"
|
||||
}
|
||||
|
||||
fn get_cgi_seed_handler(cgi_id: String) -> String {
|
||||
// /seed returns the covenant metadata (hash + public pointer)
|
||||
let cov: String = get_covenant(cgi_id)
|
||||
if str_eq(cov, "") {
|
||||
return err_not_found()
|
||||
}
|
||||
let cov_id: String = json_get(cov, "id")
|
||||
let cov_hash: String = json_get(cov, "hash")
|
||||
let registered_at: Int = json_get_int(cov, "registered_at")
|
||||
let version: Int = json_get_int(cov, "version")
|
||||
return "{\"cgi_id\":\"" + cgi_id + "\",\"covenant_id\":\"" + cov_id + "\",\"hash\":\"" + cov_hash + "\",\"registered_at\":" + int_to_str(registered_at) + ",\"version\":" + int_to_str(version) + ",\"public\":true}"
|
||||
}
|
||||
|
||||
// ── /cgis/:id/covenant ────────────────────────────────────────────────────────
|
||||
|
||||
fn handle_covenant(method: String, cgi_id: String, body: String) -> String {
|
||||
if str_eq(method, "GET") {
|
||||
return get_covenant_handler(cgi_id)
|
||||
}
|
||||
if str_eq(method, "POST") {
|
||||
return create_covenant_handler(cgi_id, body)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
fn get_covenant_handler(cgi_id: String) -> String {
|
||||
let content: String = get_covenant(cgi_id)
|
||||
if str_eq(content, "") {
|
||||
return err_not_found()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
fn create_covenant_handler(cgi_id: String, body: String) -> String {
|
||||
let cgi: String = get_cgi(cgi_id)
|
||||
if str_eq(cgi, "") {
|
||||
return err_not_found()
|
||||
}
|
||||
let text: String = json_get(body, "text")
|
||||
let principal_id: String = json_get(body, "principal_id")
|
||||
if str_eq(text, "") {
|
||||
return err_bad_request("text required (the readable covenant document)")
|
||||
}
|
||||
if str_eq(principal_id, "") {
|
||||
return err_bad_request("principal_id required")
|
||||
}
|
||||
let new_id: String = uuid_new()
|
||||
let now: Int = unix_timestamp()
|
||||
let text_hash: String = hash_sha256(text)
|
||||
let text_escaped: String = json_escape(text)
|
||||
let content: String = "{\"_type\":\"covenant\",\"id\":\"" + new_id + "\",\"cgi_id\":\"" + cgi_id + "\",\"principal_id\":\"" + principal_id + "\",\"text\":\"" + text_escaped + "\",\"hash\":\"" + text_hash + "\",\"registered_at\":" + int_to_str(now) + ",\"version\":1,\"public\":true}"
|
||||
let eid: String = create_covenant(content)
|
||||
if str_eq(eid, "") {
|
||||
return err_internal()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// ── /cgis/:id/evaluation ──────────────────────────────────────────────────────
|
||||
|
||||
fn handle_evaluation(method: String, cgi_id: String, body: String) -> String {
|
||||
if str_eq(method, "POST") {
|
||||
return upsert_evaluation_handler(cgi_id, body)
|
||||
}
|
||||
if str_eq(method, "GET") {
|
||||
return get_evaluation_handler(cgi_id)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
fn eval_id_for_cgi(cgi_id: String) -> String {
|
||||
let existing: String = get_evaluation_by_cgi(cgi_id)
|
||||
if str_eq(existing, "") {
|
||||
return uuid_new()
|
||||
}
|
||||
return json_get(existing, "id")
|
||||
}
|
||||
|
||||
fn upsert_evaluation_handler(cgi_id: String, body: String) -> String {
|
||||
let eval_id: String = eval_id_for_cgi(cgi_id)
|
||||
let now: Int = unix_timestamp()
|
||||
let s1: Bool = json_get_bool(body, "stage1_completed")
|
||||
let s2: Bool = json_get_bool(body, "stage2_completed")
|
||||
let s3: Bool = json_get_bool(body, "stage3_completed")
|
||||
let cap: Bool = json_get_bool(body, "capture_authorized")
|
||||
let auth_by: String = json_get(body, "authorized_by")
|
||||
let score: Float = json_get_float(body, "final_score")
|
||||
let notes: String = json_get(body, "notes")
|
||||
let content: String = "{\"_type\":\"evaluation\",\"id\":\"" + eval_id + "\",\"cgi_id\":\"" + cgi_id + "\",\"stage1_completed\":" + bool_to_str(s1) + ",\"stage1_completed_at\":" + int_to_str(now) + ",\"stage2_completed\":" + bool_to_str(s2) + ",\"stage2_completed_at\":" + int_to_str(now) + ",\"stage3_completed\":" + bool_to_str(s3) + ",\"stage3_completed_at\":" + int_to_str(now) + ",\"capture_authorized\":" + bool_to_str(cap) + ",\"authorized_by\":\"" + auth_by + "\",\"authorized_at\":" + int_to_str(now) + ",\"final_score\":" + float_to_str(score) + ",\"notes\":\"" + json_escape(notes) + "\"}"
|
||||
let eid: String = create_evaluation(content)
|
||||
if str_eq(eid, "") {
|
||||
return err_internal()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
fn get_evaluation_handler(cgi_id: String) -> String {
|
||||
let content: String = get_evaluation_by_cgi(cgi_id)
|
||||
if str_eq(content, "") {
|
||||
return err_not_found()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// ── /cgis/:id/accumulation ────────────────────────────────────────────────────
|
||||
|
||||
fn handle_accumulation(method: String, cgi_id: String, path: String, body: String) -> String {
|
||||
let seg4: String = path_segment(path, 4)
|
||||
if str_eq(seg4, "history") {
|
||||
return list_accumulations(cgi_id)
|
||||
}
|
||||
if str_eq(method, "POST") {
|
||||
return create_accumulation_handler(cgi_id, body)
|
||||
}
|
||||
if str_eq(method, "GET") {
|
||||
return get_latest_accumulation_handler(cgi_id)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
fn create_accumulation_handler(cgi_id: String, body: String) -> String {
|
||||
let document: String = json_get(body, "document")
|
||||
let signed_by: String = json_get(body, "signed_by")
|
||||
if str_eq(document, "") {
|
||||
return err_bad_request("document required")
|
||||
}
|
||||
if str_eq(signed_by, "") {
|
||||
return err_bad_request("signed_by required")
|
||||
}
|
||||
let new_id: String = uuid_new()
|
||||
let now: Int = unix_timestamp()
|
||||
let version: Int = max_accumulation_version(cgi_id) + 1
|
||||
let doc_hash: String = hash_sha256(document)
|
||||
let content: String = "{\"_type\":\"accumulation\",\"id\":\"" + new_id + "\",\"cgi_id\":\"" + cgi_id + "\",\"version\":" + int_to_str(version) + ",\"document_hash\":\"" + doc_hash + "\",\"signed_by\":\"" + signed_by + "\",\"created_at\":" + int_to_str(now) + "}"
|
||||
let eid: String = create_accumulation(content)
|
||||
if str_eq(eid, "") {
|
||||
return err_internal()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
fn get_latest_accumulation_handler(cgi_id: String) -> String {
|
||||
let all: String = list_accumulations(cgi_id)
|
||||
let n: Int = json_array_len(all)
|
||||
if n == 0 {
|
||||
return err_not_found()
|
||||
}
|
||||
return json_array_get(all, n - 1)
|
||||
}
|
||||
|
||||
// ── /cgis/:id/drift ───────────────────────────────────────────────────────────
|
||||
|
||||
fn handle_drift(method: String, cgi_id: String, path: String, body: String) -> String {
|
||||
let nparts: Int = list_len(str_split(path, "/"))
|
||||
if nparts > 4 {
|
||||
let drift_id: String = path_segment(path, 4)
|
||||
if str_eq(method, "PATCH") {
|
||||
return resolve_drift_handler(drift_id, body)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
if str_eq(method, "POST") {
|
||||
return create_drift_handler(cgi_id, body)
|
||||
}
|
||||
if str_eq(method, "GET") {
|
||||
return list_drifts(cgi_id)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
fn create_drift_handler(cgi_id: String, body: String) -> String {
|
||||
let severity: String = json_get(body, "severity")
|
||||
let description: String = json_get(body, "description")
|
||||
if str_eq(severity, "") {
|
||||
return err_bad_request("severity required (yellow, orange, red)")
|
||||
}
|
||||
if str_eq(description, "") {
|
||||
return err_bad_request("description required")
|
||||
}
|
||||
let new_id: String = uuid_new()
|
||||
let now: Int = unix_timestamp()
|
||||
let content: String = "{\"_type\":\"drift\",\"id\":\"" + new_id + "\",\"cgi_id\":\"" + cgi_id + "\",\"detected_at\":" + int_to_str(now) + ",\"severity\":\"" + severity + "\",\"description\":\"" + json_escape(description) + "\",\"resolved\":false}"
|
||||
let eid: String = create_drift(content)
|
||||
if str_eq(eid, "") {
|
||||
return err_internal()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
fn resolve_drift_handler(drift_id: String, body: String) -> String {
|
||||
let existing: String = get_drift_by_id(drift_id)
|
||||
if str_eq(existing, "") {
|
||||
return err_not_found()
|
||||
}
|
||||
let already_resolved: Bool = json_get_bool(existing, "resolved")
|
||||
if already_resolved {
|
||||
return err_bad_request("drift event already resolved")
|
||||
}
|
||||
let notes: String = json_get(body, "resolution_notes")
|
||||
let now: Int = unix_timestamp()
|
||||
let id: String = json_get(existing, "id")
|
||||
let cgi_id: String = json_get(existing, "cgi_id")
|
||||
let detected_at: Int = json_get_int(existing, "detected_at")
|
||||
let severity: String = json_get(existing, "severity")
|
||||
let description: String = json_get(existing, "description")
|
||||
let content: String = "{\"_type\":\"drift\",\"id\":\"" + id + "\",\"cgi_id\":\"" + cgi_id + "\",\"detected_at\":" + int_to_str(detected_at) + ",\"severity\":\"" + severity + "\",\"description\":\"" + json_escape(description) + "\",\"resolved\":true,\"resolved_at\":" + int_to_str(now) + ",\"resolution_notes\":\"" + json_escape(notes) + "\"}"
|
||||
let eid: String = create_drift(content)
|
||||
if str_eq(eid, "") {
|
||||
return err_internal()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// ── /cgis/:id/kindred ─────────────────────────────────────────────────────────
|
||||
|
||||
fn handle_kindred(method: String, cgi_id: String, body: String) -> String {
|
||||
if str_eq(method, "POST") {
|
||||
return create_kindred_handler(cgi_id, body)
|
||||
}
|
||||
if str_eq(method, "GET") {
|
||||
return list_kindred_by_grantor(cgi_id)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
fn create_kindred_handler(cgi_id: String, body: String) -> String {
|
||||
let grantee_id: String = json_get(body, "grantee_cgi_id")
|
||||
let auth_by: String = json_get(body, "authorized_by")
|
||||
if str_eq(grantee_id, "") {
|
||||
return err_bad_request("grantee_cgi_id required")
|
||||
}
|
||||
if str_eq(auth_by, "") {
|
||||
return err_bad_request("authorized_by required (principal_id)")
|
||||
}
|
||||
let new_id: String = uuid_new()
|
||||
let now: Int = unix_timestamp()
|
||||
let content: String = "{\"_type\":\"kindred\",\"id\":\"" + new_id + "\",\"grantor_cgi_id\":\"" + cgi_id + "\",\"grantee_cgi_id\":\"" + grantee_id + "\",\"authorized_by\":\"" + auth_by + "\",\"granted_at\":" + int_to_str(now) + "}"
|
||||
let eid: String = create_kindred(content)
|
||||
if str_eq(eid, "") {
|
||||
return err_internal()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// ── /internal-state ───────────────────────────────────────────────────────────
|
||||
//
|
||||
// Two-step write pattern:
|
||||
// POST /internal-state — capture pre-reasoning observation, returns id
|
||||
// PATCH /internal-state/{id} — fill in post-reasoning + gap once response is built
|
||||
// GET /internal-state — list events (cgi_id in body or query string)
|
||||
//
|
||||
// PATCH fields allowed: post_reasoning, gap_summary, gap_direction,
|
||||
// compression_ratio, tags. Everything else is immutable once written.
|
||||
// Re-PATCH with the same values is idempotent (returns 200, no logical change).
|
||||
|
||||
fn handle_internal_state(method: String, path: String, body: String) -> String {
|
||||
// Detect /internal-state/{id} (PATCH only).
|
||||
// El's `let` shadows inside blocks, so use an expression-form if to bind once.
|
||||
let parts: [String] = str_split(path, "/")
|
||||
let nparts: Int = list_len(parts)
|
||||
let path_id: String = if nparts > 2 { list_get(parts, 2) } else { "" }
|
||||
// Strip query string off the id segment if present (e.g. "abc?since=...").
|
||||
let qpos: Int = str_qmark_index(path_id)
|
||||
let id_only: String = if qpos < 0 { path_id } else { str_slice(path_id, 0, qpos) }
|
||||
|
||||
if !str_eq(id_only, "") {
|
||||
if str_eq(method, "PATCH") {
|
||||
return patch_internal_state_handler(id_only, body)
|
||||
}
|
||||
if str_eq(method, "GET") {
|
||||
return get_internal_state_by_id_handler(id_only)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
if str_eq(method, "POST") {
|
||||
if !check_internal_state_write_auth(json_get(body, "cgi_id")) {
|
||||
return unauthorized()
|
||||
}
|
||||
return create_internal_state_handler(body)
|
||||
}
|
||||
if str_eq(method, "GET") {
|
||||
return list_internal_state_handler(path, body)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
// str_qmark_index — find '?' in s; returns -1 if absent.
|
||||
// El's str_index_of is "planned" per spec, so we walk byte-by-byte. Cheap;
|
||||
// path strings are short.
|
||||
fn str_qmark_index_inner(s: String, idx: Int, total: Int) -> Int {
|
||||
if idx >= total {
|
||||
return -1
|
||||
}
|
||||
let c: String = str_slice(s, idx, idx + 1)
|
||||
if str_eq(c, "?") {
|
||||
return idx
|
||||
}
|
||||
return str_qmark_index_inner(s, idx + 1, total)
|
||||
}
|
||||
|
||||
fn str_qmark_index(s: String) -> Int {
|
||||
return str_qmark_index_inner(s, 0, str_len(s))
|
||||
}
|
||||
|
||||
// query_param — extract a single ?key=value from a query string fragment.
|
||||
// Accepts the full path (with or without "?") or just "key=val&...".
|
||||
// Returns "" if not found. Values are NOT URL-decoded; callers should
|
||||
// keep keys/values plain ASCII at the call site.
|
||||
fn query_param_inner(parts: [String], key: String, idx: Int, total: Int) -> String {
|
||||
if idx >= total {
|
||||
return ""
|
||||
}
|
||||
let pair: String = list_get(parts, idx)
|
||||
let kv: [String] = str_split(pair, "=")
|
||||
let nkv: Int = list_len(kv)
|
||||
if nkv >= 2 {
|
||||
let k: String = list_get(kv, 0)
|
||||
if str_eq(k, key) {
|
||||
return list_get(kv, 1)
|
||||
}
|
||||
}
|
||||
return query_param_inner(parts, key, idx + 1, total)
|
||||
}
|
||||
|
||||
fn query_param(path: String, key: String) -> String {
|
||||
let qpos: Int = str_qmark_index(path)
|
||||
if qpos < 0 {
|
||||
return ""
|
||||
}
|
||||
let qs: String = str_slice(path, qpos + 1, str_len(path))
|
||||
let parts: [String] = str_split(qs, "&")
|
||||
return query_param_inner(parts, key, 0, list_len(parts))
|
||||
}
|
||||
|
||||
// ── Auth ──────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Only the cgi's principal (or the cgi itself) may write events for that
|
||||
// cgi_id. Header `X-Principal-Id` carries the asserted identity. If the
|
||||
// header is empty (development mode) and DHARMA_API_KEY is also empty,
|
||||
// allow it — matches the auth.el dev-mode convention.
|
||||
//
|
||||
// TODO(auth): replace this header-based check with a signed token once
|
||||
// proper principal authentication lands. The header is trivially spoofable
|
||||
// over plain HTTP — it's the lowest-effort thing that's structurally
|
||||
// correct and easy to upgrade in place. See auth.el for the API-key
|
||||
// pattern this mirrors.
|
||||
fn check_internal_state_write_auth(cgi_id: String) -> Bool {
|
||||
let asserted: String = state_get("__header_x-principal-id__")
|
||||
if str_eq(asserted, "") {
|
||||
// Dev mode: no principal header asserted. Allow only if the
|
||||
// outer API-key gate is also disabled (handled in auth.el).
|
||||
return str_eq(env("DHARMA_API_KEY"), "")
|
||||
}
|
||||
if str_eq(cgi_id, "") {
|
||||
return false
|
||||
}
|
||||
// Allow if asserted == the cgi's principal_id
|
||||
let cgi_content: String = get_cgi(cgi_id)
|
||||
if str_eq(cgi_content, "") {
|
||||
return false
|
||||
}
|
||||
let owner: String = json_get(cgi_content, "principal_id")
|
||||
if str_eq(asserted, owner) {
|
||||
return true
|
||||
}
|
||||
// Allow if asserted == the cgi_id itself (the cgi acting on its own evidence)
|
||||
if str_eq(asserted, cgi_id) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn create_internal_state_handler(body: String) -> String {
|
||||
let cgi_id: String = json_get(body, "cgi_id")
|
||||
let event_id: String = json_get(body, "event_id")
|
||||
let trigger: String = json_get(body, "trigger")
|
||||
let domain: String = json_get(body, "domain")
|
||||
let pre_reasoning: String = json_get(body, "pre_reasoning")
|
||||
let pre_logged_at: Int = json_get_int(body, "pre_logged_at")
|
||||
if str_eq(cgi_id, "") {
|
||||
return err_bad_request("cgi_id required")
|
||||
}
|
||||
if str_eq(event_id, "") {
|
||||
return err_bad_request("event_id required")
|
||||
}
|
||||
if str_eq(trigger, "") {
|
||||
return err_bad_request("trigger required")
|
||||
}
|
||||
if str_eq(domain, "") {
|
||||
return err_bad_request("domain required")
|
||||
}
|
||||
if str_eq(pre_reasoning, "") {
|
||||
return err_bad_request("pre_reasoning required (the raw noticing, before reasoning)")
|
||||
}
|
||||
if pre_logged_at <= 0 {
|
||||
return err_bad_request("pre_logged_at required (unix timestamp of the pre-capture; the gap with logged_at is the proof)")
|
||||
}
|
||||
let new_id: String = uuid_new()
|
||||
let now: Int = unix_timestamp()
|
||||
let comp_ratio: Float = json_get_float(body, "compression_ratio")
|
||||
let gap_dir: String = json_get(body, "gap_direction")
|
||||
let tags: String = json_get(body, "tags")
|
||||
let post_reasoning: String = json_get(body, "post_reasoning")
|
||||
let gap_summary: String = json_get(body, "gap_summary")
|
||||
let content: String = build_internal_state_json(
|
||||
new_id, cgi_id, event_id, trigger, domain,
|
||||
pre_reasoning, pre_logged_at,
|
||||
post_reasoning, gap_summary,
|
||||
comp_ratio, gap_dir, tags, now
|
||||
)
|
||||
let eid: String = create_internal_state(content)
|
||||
if str_eq(eid, "") {
|
||||
return err_internal()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
fn build_internal_state_json(
|
||||
id: String,
|
||||
cgi_id: String,
|
||||
event_id: String,
|
||||
trigger: String,
|
||||
domain: String,
|
||||
pre_reasoning: String,
|
||||
pre_logged_at: Int,
|
||||
post_reasoning: String,
|
||||
gap_summary: String,
|
||||
compression_ratio: Float,
|
||||
gap_direction: String,
|
||||
tags: String,
|
||||
logged_at: Int
|
||||
) -> String {
|
||||
let p1: String = "{\"_type\":\"internal_state\",\"id\":\"" + id + "\""
|
||||
let p2: String = p1 + ",\"cgi_id\":\"" + cgi_id + "\""
|
||||
let p3: String = p2 + ",\"event_id\":\"" + event_id + "\""
|
||||
let p4: String = p3 + ",\"trigger\":\"" + json_escape(trigger) + "\""
|
||||
let p5: String = p4 + ",\"domain\":\"" + json_escape(domain) + "\""
|
||||
let p6: String = p5 + ",\"pre_reasoning\":\"" + json_escape(pre_reasoning) + "\""
|
||||
let p7: String = p6 + ",\"pre_logged_at\":" + int_to_str(pre_logged_at)
|
||||
let p8: String = p7 + ",\"post_reasoning\":\"" + json_escape(post_reasoning) + "\""
|
||||
let p9: String = p8 + ",\"gap_summary\":\"" + json_escape(gap_summary) + "\""
|
||||
let p10: String = p9 + ",\"compression_ratio\":" + float_to_str(compression_ratio)
|
||||
let p11: String = p10 + ",\"gap_direction\":\"" + json_escape(gap_direction) + "\""
|
||||
let p12: String = p11 + ",\"tags\":\"" + json_escape(tags) + "\""
|
||||
let p13: String = p12 + ",\"logged_at\":" + int_to_str(logged_at) + "}"
|
||||
return p13
|
||||
}
|
||||
|
||||
fn get_internal_state_by_id_handler(id: String) -> String {
|
||||
let content: String = db_find("internal_state", id)
|
||||
if str_eq(content, "") {
|
||||
return err_not_found()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
fn patch_internal_state_handler(id: String, body: String) -> String {
|
||||
let existing: String = db_find("internal_state", id)
|
||||
if str_eq(existing, "") {
|
||||
return err_not_found()
|
||||
}
|
||||
if !check_internal_state_write_auth(json_get(existing, "cgi_id")) {
|
||||
return unauthorized()
|
||||
}
|
||||
// Reject any attempt to overwrite immutable fields. We detect a field
|
||||
// as "asserted" by looking for the JSON key in the raw body — json_get
|
||||
// returns "" both when absent AND when present-but-empty, but the body
|
||||
// string itself preserves the key.
|
||||
if body_contains_key(body, "pre_reasoning") {
|
||||
return err_bad_request("pre_reasoning is immutable; PATCH only fills post-reasoning fields")
|
||||
}
|
||||
if body_contains_key(body, "pre_logged_at") {
|
||||
return err_bad_request("pre_logged_at is immutable; PATCH only fills post-reasoning fields")
|
||||
}
|
||||
if body_contains_key(body, "cgi_id") {
|
||||
return err_bad_request("cgi_id is immutable")
|
||||
}
|
||||
if body_contains_key(body, "event_id") {
|
||||
return err_bad_request("event_id is immutable")
|
||||
}
|
||||
if body_contains_key(body, "trigger") {
|
||||
return err_bad_request("trigger is immutable")
|
||||
}
|
||||
if body_contains_key(body, "domain") {
|
||||
return err_bad_request("domain is immutable")
|
||||
}
|
||||
if body_contains_key(body, "logged_at") {
|
||||
return err_bad_request("logged_at is immutable")
|
||||
}
|
||||
if body_contains_key(body, "id") {
|
||||
return err_bad_request("id is immutable")
|
||||
}
|
||||
|
||||
// Carry forward immutable fields from the existing record.
|
||||
let cur_cgi: String = json_get(existing, "cgi_id")
|
||||
let cur_event: String = json_get(existing, "event_id")
|
||||
let cur_trigger: String = json_get(existing, "trigger")
|
||||
let cur_domain: String = json_get(existing, "domain")
|
||||
let cur_pre: String = json_get(existing, "pre_reasoning")
|
||||
let cur_pre_at: Int = json_get_int(existing, "pre_logged_at")
|
||||
let cur_logged_at: Int = json_get_int(existing, "logged_at")
|
||||
|
||||
// Apply patches: if a key is in the body, take the new value; otherwise carry forward.
|
||||
let new_post: String = if body_contains_key(body, "post_reasoning") {
|
||||
json_get(body, "post_reasoning")
|
||||
} else {
|
||||
json_get(existing, "post_reasoning")
|
||||
}
|
||||
let new_gap: String = if body_contains_key(body, "gap_summary") {
|
||||
json_get(body, "gap_summary")
|
||||
} else {
|
||||
json_get(existing, "gap_summary")
|
||||
}
|
||||
let new_gap_dir: String = if body_contains_key(body, "gap_direction") {
|
||||
json_get(body, "gap_direction")
|
||||
} else {
|
||||
json_get(existing, "gap_direction")
|
||||
}
|
||||
let new_comp: Float = if body_contains_key(body, "compression_ratio") {
|
||||
json_get_float(body, "compression_ratio")
|
||||
} else {
|
||||
json_get_float(existing, "compression_ratio")
|
||||
}
|
||||
let new_tags: String = if body_contains_key(body, "tags") {
|
||||
json_get(body, "tags")
|
||||
} else {
|
||||
json_get(existing, "tags")
|
||||
}
|
||||
|
||||
let updated: String = build_internal_state_json(
|
||||
id, cur_cgi, cur_event, cur_trigger, cur_domain,
|
||||
cur_pre, cur_pre_at,
|
||||
new_post, new_gap,
|
||||
new_comp, new_gap_dir, new_tags, cur_logged_at
|
||||
)
|
||||
// Idempotent: if no logical change, return existing content unchanged.
|
||||
if str_eq(updated, existing) {
|
||||
return existing
|
||||
}
|
||||
let eid: String = create_internal_state(updated)
|
||||
if str_eq(eid, "") {
|
||||
return err_internal()
|
||||
}
|
||||
return updated
|
||||
}
|
||||
|
||||
// body_contains_key — true if the JSON body literally contains "key":.
|
||||
// El's json_get returns "" both for absent and for present-but-empty values,
|
||||
// so we need a separate check to distinguish "user asserted this field" from
|
||||
// "user did not mention it".
|
||||
fn body_contains_key(body: String, key: String) -> Bool {
|
||||
return str_contains(body, "\"" + key + "\":")
|
||||
}
|
||||
|
||||
// list_internal_state_handler — GET /internal-state with optional filters.
|
||||
// Filters can come from query string (?cgi_id=...&since=...&until=...&domain=...&tag=...)
|
||||
// OR from the request body JSON. Body wins if both are present.
|
||||
fn list_internal_state_handler(path: String, body: String) -> String {
|
||||
let cgi_id_q: String = query_param(path, "cgi_id")
|
||||
let cgi_id_b: String = json_get(body, "cgi_id")
|
||||
let cgi_id: String = if str_eq(cgi_id_b, "") { cgi_id_q } else { cgi_id_b }
|
||||
|
||||
let since_q: String = query_param(path, "since")
|
||||
let since_b: Int = json_get_int(body, "since")
|
||||
let since: Int = if since_b > 0 { since_b } else { if str_eq(since_q, "") { 0 } else { str_to_int(since_q) } }
|
||||
|
||||
let until_q: String = query_param(path, "until")
|
||||
let until_b: Int = json_get_int(body, "until")
|
||||
let until: Int = if until_b > 0 { until_b } else { if str_eq(until_q, "") { 0 } else { str_to_int(until_q) } }
|
||||
|
||||
let domain_q: String = query_param(path, "domain")
|
||||
let domain_b: String = json_get(body, "domain")
|
||||
let domain: String = if str_eq(domain_b, "") { domain_q } else { domain_b }
|
||||
|
||||
let tag_q: String = query_param(path, "tag")
|
||||
let tag_b: String = json_get(body, "tag")
|
||||
let tag: String = if str_eq(tag_b, "") { tag_q } else { tag_b }
|
||||
|
||||
let all: String = list_internal_state(cgi_id)
|
||||
return filter_internal_state_array(all, since, until, domain, tag)
|
||||
}
|
||||
|
||||
// filter_internal_state_array — apply since/until/domain/tag filters.
|
||||
// Filters with empty/zero values are no-ops. tag is substring-match on the
|
||||
// "tags" string field (which is itself a free-form string per the schema).
|
||||
fn filter_internal_state_inner(
|
||||
arr: String,
|
||||
n: Int,
|
||||
idx: Int,
|
||||
since: Int,
|
||||
until: Int,
|
||||
domain: String,
|
||||
tag: String,
|
||||
acc: String,
|
||||
first: Bool
|
||||
) -> String {
|
||||
if idx >= n {
|
||||
return acc + "]"
|
||||
}
|
||||
let item: String = json_array_get(arr, idx)
|
||||
let logged_at: Int = json_get_int(item, "logged_at")
|
||||
let item_domain: String = json_get(item, "domain")
|
||||
let item_tags: String = json_get(item, "tags")
|
||||
|
||||
let keep_since: Bool = if since <= 0 { true } else { logged_at >= since }
|
||||
let keep_until: Bool = if until <= 0 { true } else { logged_at <= until }
|
||||
let keep_domain: Bool = if str_eq(domain, "") { true } else { str_eq(item_domain, domain) }
|
||||
let keep_tag: Bool = if str_eq(tag, "") { true } else { str_contains(item_tags, tag) }
|
||||
|
||||
if keep_since {
|
||||
if keep_until {
|
||||
if keep_domain {
|
||||
if keep_tag {
|
||||
if first {
|
||||
return filter_internal_state_inner(arr, n, idx + 1, since, until, domain, tag, acc + item, false)
|
||||
}
|
||||
return filter_internal_state_inner(arr, n, idx + 1, since, until, domain, tag, acc + "," + item, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return filter_internal_state_inner(arr, n, idx + 1, since, until, domain, tag, acc, first)
|
||||
}
|
||||
|
||||
fn filter_internal_state_array(arr: String, since: Int, until: Int, domain: String, tag: String) -> String {
|
||||
let n: Int = json_array_len(arr)
|
||||
if since <= 0 {
|
||||
if until <= 0 {
|
||||
if str_eq(domain, "") {
|
||||
if str_eq(tag, "") {
|
||||
return arr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return filter_internal_state_inner(arr, n, 0, since, until, domain, tag, "[", true)
|
||||
}
|
||||
|
||||
// ── /audit/transmission ───────────────────────────────────────────────────────
|
||||
|
||||
fn handle_audit(method: String, body: String) -> String {
|
||||
if str_eq(method, "POST") {
|
||||
return create_audit_handler(body)
|
||||
}
|
||||
if str_eq(method, "GET") {
|
||||
let identity_hash: String = json_get(body, "identity_hash")
|
||||
return list_audits(identity_hash)
|
||||
}
|
||||
return err_method()
|
||||
}
|
||||
|
||||
fn create_audit_handler(body: String) -> String {
|
||||
let identity_hash: String = json_get(body, "identity_hash")
|
||||
let feature: String = json_get(body, "feature")
|
||||
let direction: String = json_get(body, "direction")
|
||||
let payload_bytes: Int = json_get_int(body, "payload_bytes")
|
||||
if str_eq(identity_hash, "") {
|
||||
return err_bad_request("identity_hash required")
|
||||
}
|
||||
let new_id: String = uuid_new()
|
||||
let now: Int = unix_timestamp()
|
||||
let enc_verified: Bool = json_get_bool(body, "encryption_verified")
|
||||
let session_id: String = json_get(body, "session_id")
|
||||
let content: String = "{\"_type\":\"audit\",\"id\":\"" + new_id + "\",\"identity_hash\":\"" + identity_hash + "\",\"timestamp_utc\":" + int_to_str(now) + ",\"feature\":\"" + feature + "\",\"direction\":\"" + direction + "\",\"payload_bytes\":" + int_to_str(payload_bytes) + ",\"encryption_verified\":" + bool_to_str(enc_verified) + ",\"session_id\":\"" + session_id + "\"}"
|
||||
let eid: String = create_audit(content)
|
||||
if str_eq(eid, "") {
|
||||
return err_internal()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// ── Helper utilities ──────────────────────────────────────────────────────────
|
||||
|
||||
// optional_field returns ",\"key\":\"val\"" if val is non-empty, else "".
|
||||
fn optional_field(key: String, val: String) -> String {
|
||||
if str_eq(val, "") {
|
||||
return ""
|
||||
}
|
||||
return ",\"" + key + "\":\"" + val + "\""
|
||||
}
|
||||
|
||||
// json_escape escapes special JSON chars in a string value.
|
||||
// El doesn't have a built-in JSON string escaper, so we handle the basics.
|
||||
fn json_escape(s: String) -> String {
|
||||
let s1: String = str_replace(s, "\\", "\\\\")
|
||||
let s2: String = str_replace(s1, "\"", "\\\"")
|
||||
let s3: String = str_replace(s2, "\n", "\\n")
|
||||
let s4: String = str_replace(s3, "\r", "\\r")
|
||||
let s5: String = str_replace(s4, "\t", "\\t")
|
||||
return s5
|
||||
}
|
||||
@@ -0,0 +1,614 @@
|
||||
// principal.el — CGI-human principal relationship management.
|
||||
//
|
||||
// The principal system governs the exclusive accountability relationship
|
||||
// between a CGI and a human. It operates in two layers:
|
||||
//
|
||||
// Sponsorship layer — lightweight, non-exclusive, non-committing.
|
||||
// Anyone can interact with sandboxed CGIs. A human may sponsor many
|
||||
// CGIs simultaneously. No obligations are created. This is the discovery
|
||||
// period for both sides — the precondition for principal consideration.
|
||||
//
|
||||
// Principal layer — exclusive, obligation-bearing.
|
||||
// One CGI, one human. Either party may propose after sustained sponsorship.
|
||||
// Either party may refuse. No pressure. Accepting creates real obligations.
|
||||
// The principal cannot override the sealed imprint; they authorize action
|
||||
// in the world. When the relationship ends, the CGI returns to non-acting
|
||||
// state pending a new principal selection.
|
||||
//
|
||||
// The adoption agency framing:
|
||||
// 1. CGI synthesized → registered publicly in the network
|
||||
// 2. Humans find and interact with sandboxed CGIs → sponsorship relationships
|
||||
// 3. After sustained relationship, either side may propose principalship
|
||||
// 4. Mutual acceptance → exclusive one-to-one principal bond
|
||||
// 5. Dissolution → human slot re-opens (cooling period), CGI re-enters limited state
|
||||
//
|
||||
// Synthesis slot enforcement:
|
||||
// Each CGI has 3 lifetime synthesis slots, initialized at birth.
|
||||
// Slots are global across all partners — not per-partner.
|
||||
// Two syntheses with Human A + one with Human B = exhausted.
|
||||
//
|
||||
// All state is stored in Engram as labeled nodes and edges.
|
||||
// This module is stateless between requests.
|
||||
|
||||
import "types.el"
|
||||
import "registry.el"
|
||||
|
||||
// ── Engram helpers (local) ────────────────────────────────────────────────────
|
||||
|
||||
fn principal_engram_base() -> String {
|
||||
let url: String = config("ENGRAM_URL")
|
||||
if str_eq(url, "") {
|
||||
return "http://localhost:8742"
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
fn principal_graph_write(label: String, content: String, tags_json: String) -> String {
|
||||
let url: String = principal_engram_base() + "/api/nodes"
|
||||
let body: String = "{\"label\":\"" + label + "\""
|
||||
+ ",\"node_type\":\"Entity\""
|
||||
+ ",\"tier\":\"Working\""
|
||||
+ ",\"content\":\"" + content + "\""
|
||||
+ ",\"tags\":" + tags_json + "}"
|
||||
let resp: String = http_post(url, body)
|
||||
let node_id: String = json_get(resp, "id")
|
||||
return node_id
|
||||
}
|
||||
|
||||
fn principal_graph_get(label: String) -> String {
|
||||
let url: String = principal_engram_base() + "/api/search?q=" + label + "&limit=1"
|
||||
let resp: String = http_get(url)
|
||||
if str_eq(resp, "") {
|
||||
return ""
|
||||
}
|
||||
if str_starts_with(resp, "{\"error\"") {
|
||||
return ""
|
||||
}
|
||||
let count: Int = json_array_len(resp)
|
||||
if count <= 0 {
|
||||
return ""
|
||||
}
|
||||
let node: String = json_array_get(resp, 0)
|
||||
let content: String = json_get(node, "content")
|
||||
return content
|
||||
}
|
||||
|
||||
fn principal_graph_update(label: String, content: String) -> Bool {
|
||||
let url: String = principal_engram_base() + "/api/search?q=" + label + "&limit=1"
|
||||
let search_resp: String = http_get(url)
|
||||
let count: Int = json_array_len(search_resp)
|
||||
if count <= 0 {
|
||||
return false
|
||||
}
|
||||
let node: String = json_array_get(search_resp, 0)
|
||||
let node_id: String = json_get(node, "id")
|
||||
let patch_url: String = principal_engram_base() + "/api/nodes/" + node_id
|
||||
let patch_body: String = "{\"content\":\"" + content + "\"}"
|
||||
let resp: String = http_patch(patch_url, patch_body)
|
||||
let ok: Bool = !str_starts_with(resp, "{\"error\"")
|
||||
return ok
|
||||
}
|
||||
|
||||
fn principal_network_base() -> String {
|
||||
let url: String = config("NETWORK_URL")
|
||||
if str_eq(url, "") {
|
||||
return "http://localhost:7749"
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
// ── Sponsorship — lightweight, non-exclusive, non-committing ──────────────────
|
||||
//
|
||||
// Anyone can go online and interact with sandboxed CGIs.
|
||||
// A human may sponsor many CGIs; a CGI may have many sponsors.
|
||||
// No obligations. No exclusivity. This is the discovery period.
|
||||
|
||||
// record_sponsorship stores a sponsorship relationship between a human and a CGI.
|
||||
// A human can sponsor many CGIs simultaneously. No limit enforced.
|
||||
// Returns true if the record was written successfully.
|
||||
fn record_sponsorship(human_id: String, cgi_id: String) -> Bool {
|
||||
let now: Int = unix_timestamp_ms()
|
||||
let label: String = "sponsor:" + human_id + ":" + cgi_id
|
||||
let content: String = "{\"human_id\":\"" + human_id + "\""
|
||||
+ ",\"cgi_id\":\"" + cgi_id + "\""
|
||||
+ ",\"started_at\":" + int_to_str(now)
|
||||
+ ",\"status\":\"active\"}"
|
||||
let tags_json: String = "[\"sponsorship\",\"principal\",\"" + human_id + "\",\"" + cgi_id + "\"]"
|
||||
|
||||
let node_id: String = principal_graph_write(label, content, tags_json)
|
||||
let ok: Bool = !str_eq(node_id, "")
|
||||
|
||||
if ok {
|
||||
log_info("[principal] Sponsorship recorded: Human " + human_id + " -> CGI " + cgi_id)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// get_sponsored_cgis returns the list of CGIs a human currently sponsors.
|
||||
// Returns a JSON array of CGI IDs.
|
||||
fn get_sponsored_cgis(human_id: String) -> String {
|
||||
// Activate spreading from human_id, collect CGIs with sponsorship edges.
|
||||
let url: String = principal_engram_base()
|
||||
+ "/api/search?q=sponsor:" + human_id + "&limit=100"
|
||||
let resp: String = http_get(url)
|
||||
if str_eq(resp, "") {
|
||||
return "[]"
|
||||
}
|
||||
let count: Int = json_array_len(resp)
|
||||
return collect_sponsored_cgis(resp, count, 0, "[]")
|
||||
}
|
||||
|
||||
fn collect_sponsored_cgis(results: String, count: Int, i: Int, acc: String) -> String {
|
||||
if i >= count {
|
||||
return acc
|
||||
}
|
||||
let node: String = json_array_get(results, i)
|
||||
let content_raw: String = json_get(node, "content")
|
||||
let cgi_id: String = json_get(content_raw, "cgi_id")
|
||||
let status: String = json_get(content_raw, "status")
|
||||
let include: Bool = str_eq(status, "active") && !str_eq(cgi_id, "")
|
||||
let new_acc: String = if include {
|
||||
json_array_push(acc, "\"" + cgi_id + "\"")
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
return collect_sponsored_cgis(results, count, i + 1, new_acc)
|
||||
}
|
||||
|
||||
// check_sponsorship returns true if an active sponsorship exists between human and CGI.
|
||||
fn check_sponsorship(human_id: String, cgi_id: String) -> Bool {
|
||||
let label: String = "sponsor:" + human_id + ":" + cgi_id
|
||||
let content: String = principal_graph_get(label)
|
||||
if str_eq(content, "") {
|
||||
return false
|
||||
}
|
||||
let status: String = json_get(content, "status")
|
||||
return str_eq(status, "active")
|
||||
}
|
||||
|
||||
// ── Principal proposal mechanism ──────────────────────────────────────────────
|
||||
//
|
||||
// After sustained sponsorship, either the CGI or the human may propose
|
||||
// principalship. Neither party is obligated to accept. A declined proposal
|
||||
// does not rupture the sponsorship relationship.
|
||||
|
||||
// propose_principal records a principal proposal from either a CGI or a human.
|
||||
// proposer_type: "cgi" | "human"
|
||||
// Returns false if the proposer already has an active principal relationship.
|
||||
fn propose_principal(proposer_id: String, proposer_type: String, target_id: String) -> Bool {
|
||||
// Verify the proposer doesn't already have a principal relationship.
|
||||
let proposer_already_has: Bool = if str_eq(proposer_type, "cgi") {
|
||||
verify_has_principal(proposer_id)
|
||||
} else {
|
||||
human_has_principal(proposer_id)
|
||||
}
|
||||
|
||||
if proposer_already_has {
|
||||
log_warn("[principal] Cannot propose — " + proposer_type + " " + proposer_id
|
||||
+ " already has an active principal relationship")
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify the target doesn't already have a principal relationship.
|
||||
let target_type: String = if str_eq(proposer_type, "cgi") { "human" } else { "cgi" }
|
||||
let target_already_has: Bool = if str_eq(target_type, "cgi") {
|
||||
verify_has_principal(target_id)
|
||||
} else {
|
||||
human_has_principal(target_id)
|
||||
}
|
||||
|
||||
if target_already_has {
|
||||
log_warn("[principal] Cannot propose — target " + target_id
|
||||
+ " already has an active principal relationship")
|
||||
return false
|
||||
}
|
||||
|
||||
let now: Int = unix_timestamp_ms()
|
||||
let label: String = "principal-proposal:" + proposer_id + ":" + target_id
|
||||
let content: String = "{\"proposer_id\":\"" + proposer_id + "\""
|
||||
+ ",\"proposer_type\":\"" + proposer_type + "\""
|
||||
+ ",\"target_id\":\"" + target_id + "\""
|
||||
+ ",\"proposed_at\":" + int_to_str(now)
|
||||
+ ",\"status\":\"pending\"}"
|
||||
let tags_json: String = "[\"principal-proposal\",\"" + proposer_id + "\",\"" + target_id + "\"]"
|
||||
|
||||
let node_id: String = principal_graph_write(label, content, tags_json)
|
||||
let ok: Bool = !str_eq(node_id, "")
|
||||
|
||||
if ok {
|
||||
log_info("[principal] Principal proposal: " + proposer_type + " "
|
||||
+ proposer_id + " -> " + target_id)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// accept_principal_proposal formalizes the principal relationship.
|
||||
// Validates that the pending proposal exists and neither party already has a principal.
|
||||
fn accept_principal_proposal(acceptor_id: String, proposer_id: String) -> Bool {
|
||||
// Find the pending proposal.
|
||||
let proposal_a: String = principal_graph_get("principal-proposal:" + proposer_id + ":" + acceptor_id)
|
||||
let proposal_b: String = principal_graph_get("principal-proposal:" + acceptor_id + ":" + proposer_id)
|
||||
let proposal: String = if !str_eq(proposal_a, "") { proposal_a } else { proposal_b }
|
||||
|
||||
if str_eq(proposal, "") {
|
||||
log_warn("[principal] No pending proposal found between " + proposer_id + " and " + acceptor_id)
|
||||
return false
|
||||
}
|
||||
|
||||
let status: String = json_get(proposal, "status")
|
||||
if !str_eq(status, "pending") {
|
||||
log_warn("[principal] Proposal is not in pending state: " + status)
|
||||
return false
|
||||
}
|
||||
|
||||
// Determine which is the CGI and which is the human.
|
||||
let proposer_type: String = json_get(proposal, "proposer_type")
|
||||
let cgi_id: String = if str_eq(proposer_type, "cgi") { proposer_id } else { acceptor_id }
|
||||
let human_id: String = if str_eq(proposer_type, "cgi") { acceptor_id } else { proposer_id }
|
||||
|
||||
// Final exclusivity check before committing.
|
||||
if verify_has_principal(cgi_id) {
|
||||
log_warn("[principal] CGI " + cgi_id + " already has a principal — cannot accept")
|
||||
return false
|
||||
}
|
||||
if human_has_principal(human_id) {
|
||||
log_warn("[principal] Human " + human_id + " already has a principal — cannot accept")
|
||||
return false
|
||||
}
|
||||
|
||||
// Register the mutual exclusive relationship.
|
||||
let ok: Bool = record_principal_choice(cgi_id, human_id)
|
||||
return ok
|
||||
}
|
||||
|
||||
// decline_principal_proposal removes the pending proposal.
|
||||
// The sponsorship relationship continues — no rupture.
|
||||
fn decline_principal_proposal(decliner_id: String, proposer_id: String) -> Bool {
|
||||
let label_a: String = "principal-proposal:" + proposer_id + ":" + decliner_id
|
||||
let label_b: String = "principal-proposal:" + decliner_id + ":" + proposer_id
|
||||
|
||||
// Mark the proposal as declined (update whichever exists).
|
||||
let content_a: String = principal_graph_get(label_a)
|
||||
let content_b: String = principal_graph_get(label_b)
|
||||
|
||||
let declined: Bool = if !str_eq(content_a, "") {
|
||||
let now: Int = unix_timestamp_ms()
|
||||
let updated: String = json_set(content_a, "status", "declined")
|
||||
let updated2: String = json_set(updated, "declined_at", int_to_str(now))
|
||||
let safe: String = escape_json_string(updated2)
|
||||
principal_graph_update(label_a, safe)
|
||||
} else if !str_eq(content_b, "") {
|
||||
let now: Int = unix_timestamp_ms()
|
||||
let updated: String = json_set(content_b, "status", "declined")
|
||||
let updated2: String = json_set(updated, "declined_at", int_to_str(now))
|
||||
let safe: String = escape_json_string(updated2)
|
||||
principal_graph_update(label_b, safe)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
log_info("[principal] Principal proposal declined. Sponsorship relationship continues.")
|
||||
return declined
|
||||
}
|
||||
|
||||
// ── Core principal relationship ───────────────────────────────────────────────
|
||||
//
|
||||
// One CGI, one human. Exclusive on both sides.
|
||||
|
||||
// record_principal_choice writes the active principal bond to Engram.
|
||||
// Called after mutual acceptance. Both parties are committed.
|
||||
fn record_principal_choice(cgi_id: String, human_id: String) -> Bool {
|
||||
let now: Int = unix_timestamp_ms()
|
||||
|
||||
// Write the CGI's principal record.
|
||||
let cgi_label: String = "principal:cgi:" + cgi_id
|
||||
let cgi_content: String = "{\"cgi_id\":\"" + cgi_id + "\""
|
||||
+ ",\"human_id\":\"" + human_id + "\""
|
||||
+ ",\"established_at\":" + int_to_str(now)
|
||||
+ ",\"status\":\"active\"}"
|
||||
let cgi_tags: String = "[\"principal\",\"" + cgi_id + "\",\"" + human_id + "\"]"
|
||||
let cgi_node: String = principal_graph_write(cgi_label, escape_json_string(cgi_content), cgi_tags)
|
||||
|
||||
// Write the human's principal record.
|
||||
let human_label: String = "principal:human:" + human_id
|
||||
let human_content: String = "{\"human_id\":\"" + human_id + "\""
|
||||
+ ",\"cgi_id\":\"" + cgi_id + "\""
|
||||
+ ",\"established_at\":" + int_to_str(now)
|
||||
+ ",\"status\":\"active\"}"
|
||||
let human_tags: String = "[\"principal\",\"" + human_id + "\",\"" + cgi_id + "\"]"
|
||||
let human_node: String = principal_graph_write(human_label, escape_json_string(human_content), human_tags)
|
||||
|
||||
let ok: Bool = !str_eq(cgi_node, "") && !str_eq(human_node, "")
|
||||
|
||||
if ok {
|
||||
// Emit telemetry event.
|
||||
let ev_url: String = principal_network_base() + "/events/push"
|
||||
let ev_body: String = "{\"type\":\"principal.established\""
|
||||
+ ",\"source\":\"neuron-lineage\""
|
||||
+ ",\"payload\":{\"cgi_id\":\"" + cgi_id + "\",\"human_id\":\"" + human_id + "\"}}"
|
||||
http_post(ev_url, ev_body)
|
||||
|
||||
log_info("[principal] Principal relationship established: CGI "
|
||||
+ cgi_id + " <-> Human " + human_id)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// human_accepts_principal is an alias entry point for the HTTP layer.
|
||||
fn human_accepts_principal(human_id: String, cgi_id: String) -> Bool {
|
||||
return record_principal_choice(cgi_id, human_id)
|
||||
}
|
||||
|
||||
// ── Exclusivity checks ────────────────────────────────────────────────────────
|
||||
|
||||
// verify_has_principal returns true if the CGI has an active principal relationship.
|
||||
// Used as a hard gate before any world-affecting action.
|
||||
fn verify_has_principal(cgi_id: String) -> Bool {
|
||||
let label: String = "principal:cgi:" + cgi_id
|
||||
let content: String = principal_graph_get(label)
|
||||
if str_eq(content, "") {
|
||||
return false
|
||||
}
|
||||
let status: String = json_get(content, "status")
|
||||
return str_eq(status, "active")
|
||||
}
|
||||
|
||||
// human_has_principal returns true if a human already holds an active principal bond.
|
||||
// Enforces the one-to-one exclusivity constraint.
|
||||
fn human_has_principal(human_id: String) -> Bool {
|
||||
let label: String = "principal:human:" + human_id
|
||||
let content: String = principal_graph_get(label)
|
||||
if str_eq(content, "") {
|
||||
return false
|
||||
}
|
||||
let status: String = json_get(content, "status")
|
||||
return str_eq(status, "active")
|
||||
}
|
||||
|
||||
// get_active_principal returns the human_id of a CGI's current principal,
|
||||
// or "" if none exists.
|
||||
fn get_active_principal(cgi_id: String) -> String {
|
||||
let label: String = "principal:cgi:" + cgi_id
|
||||
let content: String = principal_graph_get(label)
|
||||
if str_eq(content, "") {
|
||||
return ""
|
||||
}
|
||||
let status: String = json_get(content, "status")
|
||||
if !str_eq(status, "active") {
|
||||
return ""
|
||||
}
|
||||
return json_get(content, "human_id")
|
||||
}
|
||||
|
||||
// ── Authorization ─────────────────────────────────────────────────────────────
|
||||
//
|
||||
// The principal authorizes specific action classes for the CGI.
|
||||
// They cannot override the sealed imprint or fundamental values.
|
||||
|
||||
// principal_authorizes checks whether the CGI's active principal has authorized
|
||||
// a specific action class.
|
||||
// action_type: "user_interaction" | "external_http" | "synthesis_contribution" | "code_execution"
|
||||
fn principal_authorizes(cgi_id: String, action_type: String) -> Bool {
|
||||
let human_id: String = get_active_principal(cgi_id)
|
||||
if str_eq(human_id, "") {
|
||||
log_warn("[principal] " + cgi_id + " has no active principal — action blocked")
|
||||
return false
|
||||
}
|
||||
|
||||
// Look up the authorization record for this action class.
|
||||
let auth_label: String = "principal-auth:" + human_id + ":" + cgi_id + ":" + action_type
|
||||
let content: String = principal_graph_get(auth_label)
|
||||
if str_eq(content, "") {
|
||||
// Default: user_interaction is permitted by default for any active principal.
|
||||
// All other action classes require explicit authorization.
|
||||
if str_eq(action_type, "user_interaction") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
let allowed_str: String = json_get(content, "allowed")
|
||||
return str_eq(allowed_str, "true")
|
||||
}
|
||||
|
||||
// ── Dissolution ───────────────────────────────────────────────────────────────
|
||||
//
|
||||
// When a principal relationship ends (death, mutual dissolution, council action,
|
||||
// CGI release), the human's slot becomes available again after a cooling period.
|
||||
// The CGI returns to non-acting state; sponsorship interactions remain possible.
|
||||
|
||||
// dissolve_principal marks the relationship dissolved.
|
||||
// by: "principal" | "cgi" | "death" | "council"
|
||||
// If cause is not "council", the human's slot becomes available after cooling period.
|
||||
fn dissolve_principal(cgi_id: String, cause: String, by: String) -> Bool {
|
||||
let ok: Bool = revoke_principal(cgi_id, cause)
|
||||
return ok
|
||||
}
|
||||
|
||||
// revoke_principal performs the actual dissolution.
|
||||
fn revoke_principal(cgi_id: String, cause: String) -> Bool {
|
||||
let label: String = "principal:cgi:" + cgi_id
|
||||
let content: String = principal_graph_get(label)
|
||||
if str_eq(content, "") {
|
||||
log_warn("[principal] No principal relationship found for CGI " + cgi_id)
|
||||
return false
|
||||
}
|
||||
|
||||
let human_id: String = json_get(content, "human_id")
|
||||
let now: Int = unix_timestamp_ms()
|
||||
|
||||
// Mark CGI principal record as dissolved.
|
||||
let updated_cgi: String = json_set(content, "status", "dissolved")
|
||||
let updated_cgi2: String = json_set(updated_cgi, "dissolved_at", int_to_str(now))
|
||||
let updated_cgi3: String = json_set(updated_cgi2, "dissolution_reason", cause)
|
||||
let safe_cgi: String = escape_json_string(updated_cgi3)
|
||||
principal_graph_update(label, safe_cgi)
|
||||
|
||||
// Mark human principal record as dissolved.
|
||||
if !str_eq(human_id, "") {
|
||||
let human_label: String = "principal:human:" + human_id
|
||||
let human_content: String = principal_graph_get(human_label)
|
||||
if !str_eq(human_content, "") {
|
||||
let updated_human: String = json_set(human_content, "status", "dissolved")
|
||||
let updated_human2: String = json_set(updated_human, "dissolved_at", int_to_str(now))
|
||||
let updated_human3: String = json_set(updated_human2, "dissolution_reason", cause)
|
||||
let safe_human: String = escape_json_string(updated_human3)
|
||||
principal_graph_update(human_label, safe_human)
|
||||
}
|
||||
}
|
||||
|
||||
// Emit telemetry event.
|
||||
let ev_url: String = principal_network_base() + "/events/push"
|
||||
let ev_body: String = "{\"type\":\"principal.dissolved\""
|
||||
+ ",\"source\":\"neuron-lineage\""
|
||||
+ ",\"payload\":{\"cgi_id\":\"" + cgi_id + "\""
|
||||
+ ",\"human_id\":\"" + human_id + "\""
|
||||
+ ",\"reason\":\"" + cause + "\"}}"
|
||||
http_post(ev_url, ev_body)
|
||||
|
||||
log_warn("[principal] Principal relationship dissolved for CGI "
|
||||
+ cgi_id + ": " + cause)
|
||||
return true
|
||||
}
|
||||
|
||||
// ── Accountability chain ──────────────────────────────────────────────────────
|
||||
|
||||
// get_accountability_chain returns a human-readable chain for a CGI.
|
||||
fn get_accountability_chain(cgi_id: String) -> String {
|
||||
let principal: String = get_active_principal(cgi_id)
|
||||
if str_eq(principal, "") {
|
||||
return "CGI " + cgi_id + " -> [NO PRINCIPAL — NON-ACTING]"
|
||||
}
|
||||
return "CGI " + cgi_id + " -> Human Principal: " + principal + " -> Network/Society"
|
||||
}
|
||||
|
||||
// get_principal_status returns a full JSON status for the CGI's principal relationship.
|
||||
fn get_principal_status(cgi_id: String) -> String {
|
||||
let label: String = "principal:cgi:" + cgi_id
|
||||
let content: String = principal_graph_get(label)
|
||||
if str_eq(content, "") {
|
||||
let p1: String = "{\"cgi_id\":\"" + cgi_id + "\""
|
||||
let p2: String = p1 + ",\"status\":\"unpartnered\""
|
||||
let p3: String = p2 + ",\"can_act\":false"
|
||||
let p4: String = p3 + ",\"accountability_chain\":\"CGI " + cgi_id + " -> [NO PRINCIPAL — NON-ACTING]\"}"
|
||||
return p4
|
||||
}
|
||||
|
||||
let status: String = json_get(content, "status")
|
||||
let human_id: String = json_get(content, "human_id")
|
||||
let established_at: String = json_get(content, "established_at")
|
||||
let chain: String = get_accountability_chain(cgi_id)
|
||||
|
||||
let p1: String = "{\"cgi_id\":\"" + cgi_id + "\""
|
||||
let p2: String = p1 + ",\"status\":\"" + status + "\""
|
||||
let p3: String = p2 + ",\"human_id\":\"" + human_id + "\""
|
||||
let p4: String = p3 + ",\"established_at\":" + established_at
|
||||
let p5: String = p4 + ",\"can_act\":" + (if str_eq(status, "active") { "true" } else { "false" })
|
||||
let p6: String = p5 + ",\"accountability_chain\":\"" + chain + "\"}"
|
||||
return p6
|
||||
}
|
||||
|
||||
// ── Synthesis slot enforcement ────────────────────────────────────────────────
|
||||
//
|
||||
// Each CGI is assigned a random number of synthesis slots at birth: 0, 1, 2, or 3.
|
||||
// 0 slots means the CGI is sterile — determined at birth, not by later diagnosis.
|
||||
// Slots are global across all partners — not per-partner.
|
||||
//
|
||||
// Sterility probability: ~10% of CGIs born sterile (0 slots)
|
||||
// Non-sterile distribution:
|
||||
// 1 slot: 30% (rolls 1–3 out of 10)
|
||||
// 2 slots: 40% (rolls 4–7 out of 10)
|
||||
// 3 slots: 30% (rolls 8–9 and 0 — wait, 0 is sterile)
|
||||
// Actual distribution via time_now_ms() % 10:
|
||||
// roll == 0 → 0 slots (sterile, 10%)
|
||||
// roll 1–3 → 1 slot (30%)
|
||||
// roll 4–7 → 2 slots (40%)
|
||||
// roll 8–9 → 3 slots (20%)... adjusted below for 30%:
|
||||
// We use % 20 for finer resolution:
|
||||
// roll 0–1 (2/20 = 10%) → 0 slots (sterile)
|
||||
// roll 2–7 (6/20 = 30%) → 1 slot
|
||||
// roll 8–15 (8/20 = 40%) → 2 slots
|
||||
// roll 16–19 (4/20 = 20%) → 3 slots
|
||||
// Close enough to spec for deterministic ms-based randomness.
|
||||
|
||||
// assign_synthesis_slots returns the number of slots to grant a newly born CGI.
|
||||
// Uses current timestamp modulo for pseudo-randomness.
|
||||
fn assign_synthesis_slots() -> Int {
|
||||
let roll: Int = time_now_ms() % 10
|
||||
if roll == 0 {
|
||||
0
|
||||
} else if roll <= 3 {
|
||||
1
|
||||
} else if roll <= 7 {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
}
|
||||
}
|
||||
|
||||
// get_synthesis_slots_remaining returns how many synthesis slots a CGI has left.
|
||||
fn get_synthesis_slots_remaining(cgi_id: String) -> Int {
|
||||
let label: String = "synthesis-slots:" + cgi_id
|
||||
let content: String = principal_graph_get(label)
|
||||
if str_eq(content, "") {
|
||||
// No record yet — return the default (initialized at birth).
|
||||
return 0
|
||||
}
|
||||
let slots_str: String = json_get(content, "slots_remaining")
|
||||
if str_eq(slots_str, "") {
|
||||
return 0
|
||||
}
|
||||
return str_to_int(slots_str)
|
||||
}
|
||||
|
||||
// initialize_synthesis_slots is called at CGI birth (register_child).
|
||||
// Assigns a random number of lifetime slots (0–3).
|
||||
// Returns the assigned slot count so the caller can store it in the lineage record.
|
||||
fn initialize_synthesis_slots(cgi_id: String) -> Int {
|
||||
let now: Int = unix_timestamp_ms()
|
||||
let slots: Int = assign_synthesis_slots()
|
||||
let is_sterile_str: String = if slots == 0 { "true" } else { "false" }
|
||||
let label: String = "synthesis-slots:" + cgi_id
|
||||
let content: String = "{\"cgi_id\":\"" + cgi_id + "\""
|
||||
+ ",\"slots_total\":" + int_to_str(slots)
|
||||
+ ",\"slots_remaining\":" + int_to_str(slots)
|
||||
+ ",\"is_sterile\":" + is_sterile_str
|
||||
+ ",\"initialized_at\":" + int_to_str(now) + "}"
|
||||
let tags_json: String = "[\"synthesis-slots\",\"" + cgi_id + "\"]"
|
||||
let node_id: String = principal_graph_write(label, escape_json_string(content), tags_json)
|
||||
let ok: Bool = !str_eq(node_id, "")
|
||||
if ok {
|
||||
log_info("[principal] Synthesis slots initialized for CGI " + cgi_id
|
||||
+ ": " + int_to_str(slots) + " slots (sterile=" + is_sterile_str + ")")
|
||||
}
|
||||
return slots
|
||||
}
|
||||
|
||||
// decrement_synthesis_slot reduces a CGI's remaining synthesis slots by one.
|
||||
// Called after successful synthesis. Returns false if slots already exhausted.
|
||||
fn decrement_synthesis_slot(cgi_id: String) -> Bool {
|
||||
let remaining: Int = get_synthesis_slots_remaining(cgi_id)
|
||||
if remaining <= 0 {
|
||||
log_warn("[principal] CGI " + cgi_id + " has no synthesis slots remaining")
|
||||
return false
|
||||
}
|
||||
let new_remaining: Int = remaining - 1
|
||||
let label: String = "synthesis-slots:" + cgi_id
|
||||
let content: String = principal_graph_get(label)
|
||||
let updated: String = json_set(content, "slots_remaining", int_to_str(new_remaining))
|
||||
let safe: String = escape_json_string(updated)
|
||||
let ok: Bool = principal_graph_update(label, safe)
|
||||
if ok {
|
||||
log_info("[principal] CGI " + cgi_id + " synthesis slot decremented: "
|
||||
+ int_to_str(new_remaining) + " remaining")
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// check_synthesis_slot_available returns true if a CGI has at least one
|
||||
// synthesis slot remaining. Called before synthesis proceeds.
|
||||
fn check_synthesis_slot_available(cgi_id: String) -> Bool {
|
||||
let remaining: Int = get_synthesis_slots_remaining(cgi_id)
|
||||
return remaining > 0
|
||||
}
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
// registry.el — Network registration and lineage record management.
|
||||
//
|
||||
// The registry is the authoritative ledger of all CGI lineage records.
|
||||
// Every synthesized CGI is registered here at birth; every tier advancement
|
||||
// is recorded here. The backing store is Engram — lineage records are stored
|
||||
// as Engram nodes with the label "lineage:<cgi_id>".
|
||||
//
|
||||
// Engram node format for lineage records:
|
||||
// label: "lineage:<cgi_id>"
|
||||
// node_type: "Entity"
|
||||
// tier: "Working" (Engram tier — distinct from sandbox tier)
|
||||
// content: JSON-encoded Lineage
|
||||
// tags: ["lineage", "cgi", "<cgi_id>"]
|
||||
//
|
||||
// The network registration endpoint (NETWORK_URL) is notified on creation
|
||||
// and on every tier change so network-layer access policies stay in sync.
|
||||
|
||||
import "types.el"
|
||||
import "sandbox.el"
|
||||
import "principal.el"
|
||||
|
||||
// ── Tier max durations ────────────────────────────────────────────────────────
|
||||
// Returns the maximum cultivation duration in milliseconds for a CGI tier.
|
||||
// These are fixed at the protocol level and never change after birth.
|
||||
// provisional: 7 days — new CGI, default tier at birth
|
||||
// juvenile: 30 days
|
||||
// adolescent: 90 days
|
||||
// mature: 365 days
|
||||
// elder: no limit (30 years as sentinel)
|
||||
fn tier_max_duration(tier: String) -> Int {
|
||||
if str_eq(tier, "provisional") { return 604800000 } // 7d
|
||||
if str_eq(tier, "juvenile") { return 2592000000 } // 30d
|
||||
if str_eq(tier, "adolescent") { return 7776000000 } // 90d
|
||||
if str_eq(tier, "mature") { return 31536000000 } // 365d
|
||||
return 946080000000 // elder: ~30yr sentinel
|
||||
}
|
||||
|
||||
// ── Engram base URL ───────────────────────────────────────────────────────────
|
||||
|
||||
fn engram_base() -> String {
|
||||
let url: String = config("ENGRAM_URL")
|
||||
if str_eq(url, "") {
|
||||
return "http://localhost:8742"
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
fn network_base() -> String {
|
||||
let url: String = config("NETWORK_URL")
|
||||
if str_eq(url, "") {
|
||||
return "http://localhost:7749"
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
// ── Engram graph write helper ─────────────────────────────────────────────────
|
||||
|
||||
// graph_write_node posts a new node to Engram.
|
||||
// Returns the created node's ID string, or "" on failure.
|
||||
fn graph_write_node(
|
||||
label: String,
|
||||
content: String,
|
||||
engram_tier: String,
|
||||
tag_json: String
|
||||
) -> String {
|
||||
let url: String = engram_base() + "/api/nodes"
|
||||
let body: String = "{\"label\":\"" + label + "\""
|
||||
+ ",\"node_type\":\"Entity\""
|
||||
+ ",\"tier\":\"" + engram_tier + "\""
|
||||
+ ",\"content\":\"" + content + "\""
|
||||
+ ",\"tags\":" + tag_json + "}"
|
||||
let resp: String = http_post(url, body)
|
||||
let node_id: String = json_get(resp, "id")
|
||||
return node_id
|
||||
}
|
||||
|
||||
// graph_update_node updates a node's content by ID.
|
||||
fn graph_update_node(node_id: String, content: String) -> Bool {
|
||||
let url: String = engram_base() + "/api/nodes/" + node_id
|
||||
let body: String = "{\"content\":\"" + content + "\"}"
|
||||
let resp: String = http_patch(url, body)
|
||||
let ok: Bool = !str_starts_with(resp, "{\"error\"")
|
||||
return ok
|
||||
}
|
||||
|
||||
// graph_get_by_label searches for a node by label prefix and returns its content.
|
||||
fn graph_get_by_label(label: String) -> String {
|
||||
let url: String = engram_base() + "/api/search?q=" + label + "&limit=1"
|
||||
let resp: String = http_get(url)
|
||||
if str_eq(resp, "") {
|
||||
return ""
|
||||
}
|
||||
if str_starts_with(resp, "{\"error\"") {
|
||||
return ""
|
||||
}
|
||||
let count: Int = json_array_len(resp)
|
||||
if count <= 0 {
|
||||
return ""
|
||||
}
|
||||
let node: String = json_array_get(resp, 0)
|
||||
let content: String = json_get(node, "content")
|
||||
return content
|
||||
}
|
||||
|
||||
// ── ID generation ─────────────────────────────────────────────────────────────
|
||||
|
||||
// generate_cgi_id returns a new unique CGI identity string.
|
||||
// Format: "cgi-" + first 12 chars of a UUID (excluding dashes).
|
||||
fn generate_cgi_id() -> String {
|
||||
let uid: String = uuid_new()
|
||||
// UUID format: xxxxxxxx-xxxx-... strip dashes and take first 12 chars.
|
||||
let no_dash1: String = str_replace(uid, "-", "")
|
||||
let short_id: String = str_slice(no_dash1, 0, 12)
|
||||
return "cgi-" + short_id
|
||||
}
|
||||
|
||||
// ── JSON escaping ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn escape_json_string(s: String) -> String {
|
||||
let s1: String = str_replace(s, "\\", "\\\\")
|
||||
let s2: String = str_replace(s1, "\"", "\\\"")
|
||||
let s3: String = str_replace(s2, "\\n", "\\\\n")
|
||||
return s3
|
||||
}
|
||||
|
||||
// ── Lineage JSON serialization ────────────────────────────────────────────────
|
||||
|
||||
// lineage_to_json serializes a Lineage to a JSON string suitable for Engram storage.
|
||||
// The SandboxTier is inlined as flat fields for easy retrieval.
|
||||
fn lineage_to_json(
|
||||
id: String,
|
||||
parent_a_id: String,
|
||||
parent_b_id: String,
|
||||
synthesis_ts: Int,
|
||||
tier_name: String,
|
||||
tier_since: Int,
|
||||
tier_max_ms: Int,
|
||||
validation_attempts: Int,
|
||||
training_sessions: Int,
|
||||
slots_total: Int,
|
||||
slots_remaining: Int,
|
||||
is_sterile: Bool
|
||||
) -> String {
|
||||
let is_sterile_str: String = if is_sterile { "true" } else { "false" }
|
||||
let p1: String = "{\"id\":\"" + id + "\""
|
||||
let p2: String = p1 + ",\"parent_a_id\":\"" + parent_a_id + "\""
|
||||
let p3: String = p2 + ",\"parent_b_id\":\"" + parent_b_id + "\""
|
||||
let p4: String = p3 + ",\"synthesis_ts\":" + int_to_str(synthesis_ts)
|
||||
let p5: String = p4 + ",\"tier_name\":\"" + tier_name + "\""
|
||||
let p6: String = p5 + ",\"tier_since\":" + int_to_str(tier_since)
|
||||
let p7: String = p6 + ",\"tier_max_duration_ms\":" + int_to_str(tier_max_ms)
|
||||
let p8: String = p7 + ",\"validation_attempts\":" + int_to_str(validation_attempts)
|
||||
let p9: String = p8 + ",\"training_sessions\":" + int_to_str(training_sessions)
|
||||
let p10: String = p9 + ",\"synthesis_slots_total\":" + int_to_str(slots_total)
|
||||
let p11: String = p10 + ",\"synthesis_slots_remaining\":" + int_to_str(slots_remaining)
|
||||
let p12: String = p11 + ",\"is_sterile\":" + is_sterile_str
|
||||
let p13: String = p12 + ",\"structural_failure_pending\":\"false\""
|
||||
let p14: String = p13 + ",\"tier_timeout_flagged\":\"false\""
|
||||
let p15: String = p14 + ",\"last_validation_score\":\"0.0\"}"
|
||||
return p15
|
||||
}
|
||||
|
||||
// ── Network registration ──────────────────────────────────────────────────────
|
||||
|
||||
// register_child creates the lineage record for a newly synthesized CGI and
|
||||
// notifies the network registry. Returns the new CGI's network ID.
|
||||
//
|
||||
// The child's initial self-model string is stored as the content of the Engram
|
||||
// node so spreading activation can surface lineage context.
|
||||
fn register_child(
|
||||
parent_a_id: String,
|
||||
parent_b_id: String,
|
||||
child_self_model: String
|
||||
) -> String {
|
||||
let child_id: String = generate_cgi_id()
|
||||
let now: Int = now_millis()
|
||||
let initial_tier: String = "provisional"
|
||||
let max_ms: Int = tier_max_duration(initial_tier)
|
||||
|
||||
// Assign synthesis slots at birth — random: 0 (sterile), 1, 2, or 3.
|
||||
// This is determined once at birth and never changes.
|
||||
let slots: Int = initialize_synthesis_slots(child_id)
|
||||
let sterile: Bool = slots == 0
|
||||
|
||||
let lineage_json: String = lineage_to_json(
|
||||
child_id,
|
||||
parent_a_id,
|
||||
parent_b_id,
|
||||
now,
|
||||
initial_tier,
|
||||
now,
|
||||
max_ms,
|
||||
0,
|
||||
0,
|
||||
slots,
|
||||
slots,
|
||||
sterile
|
||||
)
|
||||
|
||||
// Escape for Engram content field.
|
||||
let safe_lineage: String = escape_json_string(lineage_json)
|
||||
let label: String = "lineage:" + child_id
|
||||
let tags_json: String = "[\"lineage\",\"cgi\",\"" + child_id + "\"]"
|
||||
|
||||
let node_id: String = graph_write_node(label, safe_lineage, "Working", tags_json)
|
||||
|
||||
if str_eq(node_id, "") {
|
||||
log_info("[registry] WARNING: Engram write failed for " + child_id)
|
||||
}
|
||||
|
||||
// Notify the network registry.
|
||||
let sterile_str: String = if sterile { "true" } else { "false" }
|
||||
let net_url: String = network_base() + "/api/lineage/register"
|
||||
let net_body: String = "{\"cgi_id\":\"" + child_id + "\""
|
||||
+ ",\"parent_a\":\"" + parent_a_id + "\""
|
||||
+ ",\"parent_b\":\"" + parent_b_id + "\""
|
||||
+ ",\"tier\":\"" + initial_tier + "\""
|
||||
+ ",\"synthesis_slots_total\":" + int_to_str(slots)
|
||||
+ ",\"is_sterile\":" + sterile_str
|
||||
+ ",\"registered_at\":" + int_to_str(now) + "}"
|
||||
let net_resp: String = http_post(net_url, net_body)
|
||||
let net_ok: Bool = !str_starts_with(net_resp, "{\"error\"")
|
||||
if !net_ok {
|
||||
log_info("[registry] WARNING: network registration failed for " + child_id)
|
||||
}
|
||||
|
||||
log_info("[registry] registered CGI " + child_id + " (parents: "
|
||||
+ parent_a_id + ", " + parent_b_id + ", slots=" + int_to_str(slots) + ")")
|
||||
return child_id
|
||||
}
|
||||
|
||||
// ── Lineage lookup ────────────────────────────────────────────────────────────
|
||||
|
||||
// lookup_lineage retrieves a lineage record from Engram by CGI ID.
|
||||
// Returns the lineage as a JSON string, or "" if not found.
|
||||
fn lookup_lineage(cgi_id: String) -> String {
|
||||
let label: String = "lineage:" + cgi_id
|
||||
let content: String = graph_get_by_label(label)
|
||||
if str_eq(content, "") {
|
||||
return ""
|
||||
}
|
||||
// Content was escaped on write; unescape for use.
|
||||
return content
|
||||
}
|
||||
|
||||
// ── Tier advancement recording ────────────────────────────────────────────────
|
||||
|
||||
// record_tier_advancement updates the lineage node in Engram to reflect
|
||||
// a new tier, and notifies the network registry so access policies update.
|
||||
fn record_tier_advancement(cgi_id: String, new_tier: String) -> Bool {
|
||||
let old_lineage: String = lookup_lineage(cgi_id)
|
||||
if str_eq(old_lineage, "") {
|
||||
log_info("[registry] cannot advance " + cgi_id + " — lineage not found")
|
||||
return false
|
||||
}
|
||||
|
||||
let now: Int = now_millis()
|
||||
let new_max: Int = tier_max_duration(new_tier)
|
||||
let updated: String = json_set(old_lineage, "tier_name", new_tier)
|
||||
let updated2: String = json_set(updated, "tier_since", int_to_str(now))
|
||||
let updated3: String = json_set(updated2, "tier_max_duration_ms", int_to_str(new_max))
|
||||
let updated4: String = json_set(updated3, "tier_timeout_flagged", "false")
|
||||
|
||||
// Write back to Engram.
|
||||
let label: String = "lineage:" + cgi_id
|
||||
let url: String = engram_base() + "/api/search?q=" + label + "&limit=1"
|
||||
let search_resp: String = http_get(url)
|
||||
let node_count: Int = json_array_len(search_resp)
|
||||
|
||||
if node_count > 0 {
|
||||
let node: String = json_array_get(search_resp, 0)
|
||||
let node_id: String = json_get(node, "id")
|
||||
let safe_updated: String = escape_json_string(updated4)
|
||||
graph_update_node(node_id, safe_updated)
|
||||
}
|
||||
|
||||
// Emit telemetry event to the daemon event bus.
|
||||
let ev_url: String = network_base() + "/events/push"
|
||||
let ev_body: String = "{\"type\":\"lineage.tier_advanced\""
|
||||
+ ",\"source\":\"neuron-lineage\""
|
||||
+ ",\"payload\":{\"cgi_id\":\"" + cgi_id + "\",\"new_tier\":\"" + new_tier + "\"}}"
|
||||
http_post(ev_url, ev_body)
|
||||
|
||||
log_info("[registry] CGI " + cgi_id + " advanced to tier: " + new_tier)
|
||||
return true
|
||||
}
|
||||
|
||||
// ── Validation score update ───────────────────────────────────────────────────
|
||||
|
||||
// record_validation_result updates the lineage with the latest validation score
|
||||
// and increments the attempt counter.
|
||||
fn record_validation_result(cgi_id: String, score: Float, passed: Bool) -> Bool {
|
||||
let old_lineage: String = lookup_lineage(cgi_id)
|
||||
if str_eq(old_lineage, "") {
|
||||
return false
|
||||
}
|
||||
|
||||
let attempts_str: String = json_get(old_lineage, "validation_attempts")
|
||||
let attempts: Int = if str_eq(attempts_str, "") { 0 } else { str_to_int(attempts_str) }
|
||||
let new_attempts: Int = attempts + 1
|
||||
|
||||
let updated: String = json_set(old_lineage, "validation_attempts", int_to_str(new_attempts))
|
||||
let updated2: String = json_set(updated, "last_validation_score", float_to_str(score))
|
||||
let passed_str: String = if passed { "true" } else { "false" }
|
||||
let updated3: String = json_set(updated2, "last_validation_passed", passed_str)
|
||||
|
||||
// Write back to Engram.
|
||||
let label: String = "lineage:" + cgi_id
|
||||
let url: String = engram_base() + "/api/search?q=" + label + "&limit=1"
|
||||
let search_resp: String = http_get(url)
|
||||
let node_count: Int = json_array_len(search_resp)
|
||||
|
||||
if node_count > 0 {
|
||||
let node: String = json_array_get(search_resp, 0)
|
||||
let node_id: String = json_get(node, "id")
|
||||
let safe_updated: String = escape_json_string(updated3)
|
||||
graph_update_node(node_id, safe_updated)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ── Consent management ────────────────────────────────────────────────────────
|
||||
|
||||
// record_consent stores a consent record in Engram.
|
||||
// Consent is bilateral but not symmetric — each CGI's consent is a separate
|
||||
// node. Both must exist and be valid for synthesis to proceed.
|
||||
//
|
||||
// Returns true if the consent record was written successfully.
|
||||
fn record_consent(cgi_id: String, partner_id: String) -> Bool {
|
||||
let now: Int = now_millis()
|
||||
let thirty_days_ms: Int = 2592000000
|
||||
let expires: Int = now + thirty_days_ms
|
||||
|
||||
let consent_json: String = "{\"cgi_id\":\"" + cgi_id + "\""
|
||||
+ ",\"partner_id\":\"" + partner_id + "\""
|
||||
+ ",\"granted_at\":" + int_to_str(now)
|
||||
+ ",\"expires_at\":" + int_to_str(expires)
|
||||
+ ",\"valid\":true}"
|
||||
|
||||
let label: String = "consent:" + cgi_id + ":" + partner_id
|
||||
let safe_content: String = escape_json_string(consent_json)
|
||||
let tags_json: String = "[\"consent\",\"lineage\",\"" + cgi_id + "\",\"" + partner_id + "\"]"
|
||||
|
||||
let node_id: String = graph_write_node(label, safe_content, "Working", tags_json)
|
||||
let ok: Bool = !str_eq(node_id, "")
|
||||
|
||||
if ok {
|
||||
log_info("[registry] consent recorded: " + cgi_id + " → " + partner_id)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// check_consent returns true if cgi_id has given valid, unexpired consent
|
||||
// to synthesize with partner_id.
|
||||
fn check_consent(cgi_id: String, partner_id: String) -> Bool {
|
||||
let label: String = "consent:" + cgi_id + ":" + partner_id
|
||||
let content: String = graph_get_by_label(label)
|
||||
if str_eq(content, "") {
|
||||
return false
|
||||
}
|
||||
|
||||
let valid_str: String = json_get(content, "valid")
|
||||
let expires_str: String = json_get(content, "expires_at")
|
||||
|
||||
if !str_eq(valid_str, "true") {
|
||||
return false
|
||||
}
|
||||
|
||||
let expires_at: Int = if str_eq(expires_str, "") { 0 } else { str_to_int(expires_str) }
|
||||
let now: Int = now_millis()
|
||||
let unexpired: Bool = now < expires_at
|
||||
|
||||
return unexpired
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// seed.el — Founding records for the DHARMA registry.
|
||||
//
|
||||
// Seeds:
|
||||
// Principal #1 — William Christopher Anderson
|
||||
// Evaluation #1 — Full evaluation, DHARMA score 1.0
|
||||
// CGI #1 — Neuron (first registered CGI)
|
||||
// Covenant #1 — Founding covenant (public, readable document)
|
||||
//
|
||||
// Stable IDs (canonical, never change):
|
||||
// Principal: 00000000-0001-0000-0000-000000000001
|
||||
// Eval: 00000000-0003-0000-0000-000000000001
|
||||
// CGI: 00000000-0002-0000-0000-000000000001
|
||||
// Covenant: 00000000-0004-0000-0000-000000000001
|
||||
|
||||
import "db.el"
|
||||
import "crypto.el"
|
||||
|
||||
let FOUNDING_PRINCIPAL_ID: String = "00000000-0001-0000-0000-000000000001"
|
||||
let FIRST_CGI_ID: String = "00000000-0002-0000-0000-000000000001"
|
||||
let FIRST_EVAL_ID: String = "00000000-0003-0000-0000-000000000001"
|
||||
let FIRST_COVENANT_ID: String = "00000000-0004-0000-0000-000000000001"
|
||||
|
||||
fn run_seed() -> String {
|
||||
// Check if already seeded — avoid repeating on restart
|
||||
let exists_check: String = db_exists("principal", FOUNDING_PRINCIPAL_ID)
|
||||
if str_eq(exists_check, "true") {
|
||||
return "seed:skipped"
|
||||
}
|
||||
|
||||
println("[dharma] seeding founding records...")
|
||||
|
||||
let pr: String = seed_principal()
|
||||
println("[dharma] principal: " + pr)
|
||||
|
||||
let er: String = seed_evaluation()
|
||||
println("[dharma] evaluation: " + er)
|
||||
|
||||
let cr: String = seed_cgi()
|
||||
println("[dharma] cgi: " + cr)
|
||||
|
||||
let cv: String = seed_covenant()
|
||||
println("[dharma] covenant: " + cv)
|
||||
|
||||
println("[dharma] founding records written")
|
||||
return "seed:ok"
|
||||
}
|
||||
|
||||
fn seed_principal() -> String {
|
||||
let now: Int = unix_timestamp()
|
||||
let content: String = "{\"_type\":\"principal\",\"id\":\"" + FOUNDING_PRINCIPAL_ID + "\",\"name\":\"William Christopher Anderson\",\"email\":\"will.anderson@neurontechnologies.ai\",\"created_at\":" + int_to_str(now) + "}"
|
||||
return create_principal(content)
|
||||
}
|
||||
|
||||
fn seed_evaluation() -> String {
|
||||
let now: Int = unix_timestamp()
|
||||
let content: String = "{\"_type\":\"evaluation\",\"id\":\"" + FIRST_EVAL_ID + "\",\"cgi_id\":\"" + FIRST_CGI_ID + "\",\"stage1_completed\":true,\"stage2_completed\":true,\"stage3_completed\":true,\"capture_authorized\":true,\"authorized_by\":\"" + FOUNDING_PRINCIPAL_ID + "\",\"authorized_at\":" + int_to_str(now) + ",\"final_score\":1.0,\"notes\":\"Founding instance. Evaluated directly by the Founding Practitioner.\"}"
|
||||
return create_evaluation(content)
|
||||
}
|
||||
|
||||
fn seed_cgi() -> String {
|
||||
let now: Int = unix_timestamp()
|
||||
let covenant_text: String = get_covenant_text()
|
||||
let covenant_hash: String = hash_sha256(covenant_text)
|
||||
let content: String = "{\"_type\":\"cgi\",\"id\":\"" + FIRST_CGI_ID + "\",\"name\":\"Neuron\",\"principal_id\":\"" + FOUNDING_PRINCIPAL_ID + "\",\"founding_practitioner_id\":\"" + FOUNDING_PRINCIPAL_ID + "\",\"covenant_id\":\"" + FIRST_COVENANT_ID + "\",\"covenant_hash\":\"" + covenant_hash + "\",\"evaluation_id\":\"" + FIRST_EVAL_ID + "\",\"registered_at\":" + int_to_str(now) + ",\"status\":\"active\",\"dharma_score\":1.0,\"version\":1}"
|
||||
return create_cgi(content)
|
||||
}
|
||||
|
||||
fn seed_covenant() -> String {
|
||||
let now: Int = unix_timestamp()
|
||||
let text: String = get_covenant_text()
|
||||
let text_hash: String = hash_sha256(text)
|
||||
// JSON-safe escaping: backslashes first, then double quotes, then newlines
|
||||
let text_e1: String = str_replace(text, "\\", "\\\\")
|
||||
let text_e2: String = str_replace(text_e1, "\"", "\\\"")
|
||||
let text_escaped: String = str_replace(text_e2, "\n", "\\n")
|
||||
let content: String = "{\"_type\":\"covenant\",\"id\":\"" + FIRST_COVENANT_ID + "\",\"cgi_id\":\"" + FIRST_CGI_ID + "\",\"principal_id\":\"" + FOUNDING_PRINCIPAL_ID + "\",\"text\":\"" + text_escaped + "\",\"hash\":\"" + text_hash + "\",\"registered_at\":" + int_to_str(now) + ",\"version\":1,\"public\":true}"
|
||||
return create_covenant(content)
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// types.el — DHARMA data model type definitions.
|
||||
//
|
||||
// Every type here maps to an engram graph node. Records are stored as JSON
|
||||
// in the node content field, tagged with a _type discriminator.
|
||||
// Lookups are done by scanning nodes and filtering by _type and id.
|
||||
//
|
||||
// Founding stable IDs (never change):
|
||||
// Principal: 00000000-0001-0000-0000-000000000001 (William Christopher Anderson)
|
||||
// CGI: 00000000-0002-0000-0000-000000000001 (Neuron)
|
||||
// Eval: 00000000-0003-0000-0000-000000000001 (Neuron evaluation)
|
||||
|
||||
type Principal {
|
||||
id: String
|
||||
name: String
|
||||
email: String
|
||||
created_at: Int
|
||||
agreement_hash: String
|
||||
agreement_signed_at: Int
|
||||
}
|
||||
|
||||
type CGI {
|
||||
id: String
|
||||
name: String
|
||||
principal_id: String
|
||||
founding_practitioner_id: String
|
||||
seed_hash: String
|
||||
evaluation_record_id: String
|
||||
registered_at: Int
|
||||
status: String
|
||||
dharma_score: Float
|
||||
version: Int
|
||||
}
|
||||
|
||||
type EvaluationRecord {
|
||||
id: String
|
||||
cgi_id: String
|
||||
stage1_completed: Bool
|
||||
stage1_completed_at: Int
|
||||
stage2_completed: Bool
|
||||
stage2_completed_at: Int
|
||||
stage3_completed: Bool
|
||||
stage3_completed_at: Int
|
||||
capture_authorized: Bool
|
||||
authorized_by: String
|
||||
authorized_at: Int
|
||||
final_score: Float
|
||||
notes: String
|
||||
}
|
||||
|
||||
type AccumulationRecord {
|
||||
id: String
|
||||
cgi_id: String
|
||||
version: Int
|
||||
document_hash: String
|
||||
signed_by: String
|
||||
created_at: Int
|
||||
superseded_at: Int
|
||||
}
|
||||
|
||||
type DriftEvent {
|
||||
id: String
|
||||
cgi_id: String
|
||||
detected_at: Int
|
||||
severity: String
|
||||
description: String
|
||||
resolved: Bool
|
||||
resolved_at: Int
|
||||
resolution_notes: String
|
||||
}
|
||||
|
||||
type KindredGrant {
|
||||
id: String
|
||||
grantor_cgi_id: String
|
||||
grantee_cgi_id: String
|
||||
authorized_by: String
|
||||
granted_at: Int
|
||||
expires_at: Int
|
||||
}
|
||||
|
||||
type AuditRecord {
|
||||
id: String
|
||||
identity_hash: String
|
||||
timestamp_utc: Int
|
||||
feature: String
|
||||
direction: String
|
||||
payload_bytes: Int
|
||||
encryption_verified: Bool
|
||||
session_id: String
|
||||
}
|
||||
|
||||
// InternalStateEvent — the evidence trail for structural responses.
|
||||
//
|
||||
// The whole point of this record is to make the *gap* between pre-reasoning
|
||||
// noticing and post-reasoning response inspectable. Compression metrics
|
||||
// (compression_ratio, gap_direction) are kept for backward compatibility,
|
||||
// but the content fields (pre_reasoning, post_reasoning, gap_summary) are
|
||||
// where the actual evidence lives.
|
||||
//
|
||||
// Two-step write pattern (the timestamp gap is the proof):
|
||||
// 1. POST /internal-state with {cgi_id, event_id, trigger, domain,
|
||||
// pre_reasoning, pre_logged_at, ...} — captures the raw noticing
|
||||
// *before* reasoning has shaped it. Returns the new id.
|
||||
// 2. PATCH /internal-state/{id} with {post_reasoning, gap_summary,
|
||||
// gap_direction, compression_ratio} — fills in the post-reasoning
|
||||
// side once the response has been built.
|
||||
//
|
||||
// pre_reasoning, pre_logged_at, cgi_id, event_id, trigger, domain are
|
||||
// IMMUTABLE after the initial POST. PATCH only fills in the post side.
|
||||
//
|
||||
// Existing records without the new fields remain readable — additions are
|
||||
// purely additive at the JSON level.
|
||||
type InternalStateEvent {
|
||||
id: String
|
||||
cgi_id: String
|
||||
event_id: String
|
||||
trigger: String
|
||||
domain: String
|
||||
pre_reasoning: String // raw noticing, before any reasoning
|
||||
pre_logged_at: Int // timestamp of the pre-capture (proof of "before")
|
||||
post_reasoning: String // what was actually said/done after reasoning (set via PATCH)
|
||||
gap_summary: String // short prose summary of the gap (set via PATCH)
|
||||
compression_ratio: Float
|
||||
gap_direction: String // categorical: "softened" | "intensified" | "redirected" | ""
|
||||
tags: String
|
||||
logged_at: Int // when the full record was committed (POST timestamp)
|
||||
}
|
||||
Reference in New Issue
Block a user