Archived
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,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
|
||||
}
|
||||
Reference in New Issue
Block a user