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/.
615 lines
26 KiB
EmacsLisp
615 lines
26 KiB
EmacsLisp
// 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
|
||
}
|
||
|