Archived
90ddbdbfc3
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/.
376 lines
15 KiB
EmacsLisp
376 lines
15 KiB
EmacsLisp
// 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
|
|
}
|