diff --git a/dist/platform/elc b/dist/platform/elc index a4ed6f4..cffacef 100755 Binary files a/dist/platform/elc and b/dist/platform/elc differ diff --git a/dist/platform/elc.c b/dist/platform/elc.c index 23d69ba..63c864b 100644 --- a/dist/platform/elc.c +++ b/dist/platform/elc.c @@ -53,6 +53,12 @@ el_val_t params_to_c(el_val_t params); el_val_t transform_implicit_return(el_val_t body); el_val_t is_int_name(el_val_t name); el_val_t is_int_call(el_val_t call_expr); +el_val_t cap_record_violation(el_val_t kind, el_val_t fn_name); +el_val_t is_self_formation_call(el_val_t fn_name); +el_val_t is_dharma_call(el_val_t fn_name); +el_val_t is_llm_call(el_val_t fn_name); +el_val_t cap_check_call(el_val_t fn_name); +el_val_t emit_cap_violations(void); el_val_t add_int_name(el_val_t name); el_val_t build_int_names_for_params(el_val_t params); el_val_t cg_fn(el_val_t stmt); @@ -409,6 +415,9 @@ el_val_t keyword_kind(el_val_t word) { if (str_eq(word, EL_STR("cgi"))) { return EL_STR("Cgi"); } + if (str_eq(word, EL_STR("service"))) { + return EL_STR("Service"); + } if (str_eq(word, EL_STR("manager"))) { return EL_STR("Manager"); } @@ -1562,6 +1571,43 @@ el_val_t parse_stmt(el_val_t tokens, el_val_t pos) { p = expect(tokens, p, EL_STR("RBrace")); return make_result(el_map_new(10, "stmt", EL_STR("CgiBlock"), "name", name, "dharma_id", dharma_id, "principal", principal, "network", network, "engram", engram, "has_dharma_id", has_dharma_id, "has_principal", has_principal, "has_network", has_network, "has_engram", has_engram), p); } + if (str_eq(k, EL_STR("Service"))) { + el_val_t p = (pos + 1); + el_val_t name = tok_value(tokens, p); + p = (p + 1); + p = expect(tokens, p, EL_STR("LBrace")); + el_val_t sponsor = EL_STR(""); + el_val_t domain = EL_STR(""); + el_val_t running = 1; + while (running) { + el_val_t k2 = tok_kind(tokens, p); + if (str_eq(k2, EL_STR("RBrace"))) { + running = 0; + } else { + if (str_eq(k2, EL_STR("Eof"))) { + running = 0; + } else { + el_val_t fname = tok_value(tokens, p); + p = (p + 1); + p = expect(tokens, p, EL_STR("Colon")); + el_val_t fval = tok_value(tokens, p); + p = (p + 1); + if (str_eq(fname, EL_STR("sponsor"))) { + sponsor = fval; + } + if (str_eq(fname, EL_STR("domain"))) { + domain = fval; + } + el_val_t k3 = tok_kind(tokens, p); + if (str_eq(k3, EL_STR("Comma"))) { + p = (p + 1); + } + } + } + } + p = expect(tokens, p, EL_STR("RBrace")); + return make_result(el_map_new(4, "stmt", EL_STR("ServiceBlock"), "name", name, "sponsor", sponsor, "domain", domain), p); + } el_val_t r = parse_expr(tokens, pos); el_val_t val = el_get_field(r, EL_STR("node")); el_val_t p = el_get_field(r, EL_STR("pos")); @@ -1924,6 +1970,7 @@ el_val_t cg_expr(el_val_t expr) { } if (str_eq(func_kind, EL_STR("Ident"))) { el_val_t fn_name = el_get_field(func, EL_STR("name")); + cap_check_call(fn_name); return el_str_concat(el_str_concat(el_str_concat(fn_name, EL_STR("(")), args_c), EL_STR(")")); } if (str_eq(func_kind, EL_STR("Field"))) { @@ -2401,6 +2448,141 @@ el_val_t is_int_call(el_val_t call_expr) { return 0; } +el_val_t cap_record_violation(el_val_t kind, el_val_t fn_name) { + el_val_t csv = state_get(EL_STR("__cap_violations")); + if (str_eq(csv, EL_STR(""))) { + csv = EL_STR(","); + } + el_val_t entry = el_str_concat(el_str_concat(kind, EL_STR(":")), fn_name); + el_val_t key = el_str_concat(el_str_concat(EL_STR(","), entry), EL_STR(",")); + if (str_contains(csv, key)) { + return 1; + } + state_set(EL_STR("__cap_violations"), el_str_concat(el_str_concat(csv, entry), EL_STR(","))); + return 1; + return 0; +} + +el_val_t is_self_formation_call(el_val_t fn_name) { + if (str_eq(fn_name, EL_STR("llm_call_agentic"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("llm_register_tool"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("dharma_emit"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("dharma_field"))) { + return 1; + } + return 0; + return 0; +} + +el_val_t is_dharma_call(el_val_t fn_name) { + if (str_eq(fn_name, EL_STR("dharma_connect"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("dharma_send"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("dharma_activate"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("dharma_emit"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("dharma_field"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("dharma_strengthen"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("dharma_relationship"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("dharma_peers"))) { + return 1; + } + return 0; + return 0; +} + +el_val_t is_llm_call(el_val_t fn_name) { + if (str_eq(fn_name, EL_STR("llm_call"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("llm_call_system"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("llm_call_agentic"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("llm_vision"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("llm_register_tool"))) { + return 1; + } + if (str_eq(fn_name, EL_STR("llm_models"))) { + return 1; + } + return 0; + return 0; +} + +el_val_t cap_check_call(el_val_t fn_name) { + el_val_t kind = state_get(EL_STR("__program_kind")); + if (str_eq(kind, EL_STR("cgi"))) { + return 1; + } + if (str_eq(kind, EL_STR("service"))) { + if (is_self_formation_call(fn_name)) { + cap_record_violation(EL_STR("service"), fn_name); + return 0; + } + return 1; + } + if (is_dharma_call(fn_name)) { + cap_record_violation(EL_STR("utility"), fn_name); + return 0; + } + if (is_llm_call(fn_name)) { + cap_record_violation(EL_STR("utility"), fn_name); + return 0; + } + return 1; + return 0; +} + +el_val_t emit_cap_violations(void) { + el_val_t csv = state_get(EL_STR("__cap_violations")); + if (str_eq(csv, EL_STR(""))) { + return 0; + } + if (str_eq(csv, EL_STR(","))) { + return 0; + } + el_val_t n = str_len(csv); + el_val_t i = 1; + while (i < n) { + el_val_t next_comma = str_index_of(str_slice(csv, i, n), EL_STR(",")); + if (next_comma < 0) { + return 0; + } + el_val_t entry = str_slice(csv, i, (i + next_comma)); + el_val_t colon = str_index_of(entry, EL_STR(":")); + if (colon > 0) { + el_val_t kind = str_slice(entry, 0, colon); + el_val_t fn_name = str_slice(entry, (colon + 1), str_len(entry)); + emit_line(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("#error \"capability violation: '"), kind), EL_STR("' programs may not call '")), fn_name), EL_STR("' (self-formation primitive — only 'cgi' programs may use it)\""))); + } + i = ((i + next_comma) + 1); + } + return 0; +} + el_val_t add_int_name(el_val_t name) { el_val_t csv = state_get(EL_STR("__int_names")); if (str_eq(csv, EL_STR(""))) { @@ -2691,6 +2873,8 @@ el_val_t codegen(el_val_t stmts, el_val_t source) { el_val_t n_top = native_list_len(stmts); el_val_t cgi_count = 0; el_val_t cgi_block = el_map_new(1, "stmt", EL_STR("None")); + el_val_t svc_count = 0; + el_val_t svc_block = el_map_new(1, "stmt", EL_STR("None")); el_val_t ti = 0; while (ti < n_top) { el_val_t s = native_list_get(stmts, ti); @@ -2701,11 +2885,34 @@ el_val_t codegen(el_val_t stmts, el_val_t source) { cgi_block = s; } } + if (str_eq(sk, EL_STR("ServiceBlock"))) { + svc_count = (svc_count + 1); + if (svc_count == 1) { + svc_block = s; + } + } ti = (ti + 1); } if (cgi_count > 1) { emit_line(EL_STR("#error \"El: multiple cgi blocks in program (only one allowed)\"")); } + if (svc_count > 1) { + emit_line(EL_STR("#error \"El: multiple service blocks in program (only one allowed)\"")); + } + if (cgi_count >= 1) { + if (svc_count >= 1) { + emit_line(EL_STR("#error \"El: program declares both cgi and service blocks (mutually exclusive — pick one)\"")); + } + } + el_val_t kind = EL_STR("utility"); + if (cgi_count >= 1) { + kind = EL_STR("cgi"); + } + if (svc_count >= 1) { + kind = EL_STR("service"); + } + state_set(EL_STR("__program_kind"), kind); + state_set(EL_STR("__cap_violations"), EL_STR("")); emit_line(EL_STR("#include ")); emit_line(EL_STR("#include ")); emit_line(EL_STR("#include \"el_runtime.h\"")); @@ -2714,7 +2921,7 @@ el_val_t codegen(el_val_t stmts, el_val_t source) { el_val_t i = 0; while (i < n) { el_val_t stmt = native_list_get(stmts, i); - el_val_t kind = el_get_field(stmt, EL_STR("stmt")); + kind = el_get_field(stmt, EL_STR("stmt")); if (str_eq(kind, EL_STR("FnDef"))) { el_val_t fn_name = el_get_field(stmt, EL_STR("name")); if (!str_eq(fn_name, EL_STR("main"))) { @@ -2769,6 +2976,7 @@ el_val_t codegen(el_val_t stmts, el_val_t source) { emit_line(EL_STR(" return 0;")); emit_line(EL_STR("}")); emit_blank(); + emit_cap_violations(); return EL_STR(""); return 0; } diff --git a/dist/platform/elc.prev6 b/dist/platform/elc.prev6 new file mode 100755 index 0000000..80ca0d7 Binary files /dev/null and b/dist/platform/elc.prev6 differ diff --git a/el-compiler/src/codegen.el b/el-compiler/src/codegen.el index 69886fe..be0e65f 100644 --- a/el-compiler/src/codegen.el +++ b/el-compiler/src/codegen.el @@ -345,6 +345,12 @@ fn cg_expr(expr: Map) -> String { if func_kind == "Ident" { let fn_name: String = func["name"] + // Capability-kind enforcement: services can't call + // self-formation primitives; utilities can't call any + // DHARMA or LLM primitives. cap_check_call records + // violations to be emitted as #error directives at the + // top of the generated C, so cc fails with a clear msg. + cap_check_call(fn_name) return fn_name + "(" + args_c + ")" } @@ -807,6 +813,114 @@ fn is_int_call(call_expr: Map) -> Bool { return false } +// ── Capability-kind enforcement ────────────────────────────────────────────── +// +// A program's top-level block (cgi / service / none) determines which +// runtime primitives it may call. The compiler records violations in +// process state during cg_expr's Call emission; codegen's entry point +// then emits #error directives at the top of the generated C so the +// downstream cc step fails with a clear message. +// +// Capability tiers: +// "cgi" — full self-formation. All primitives. +// "service" — bounded. Cannot call self-formation primitives: +// llm_call_agentic, llm_register_tool, dharma_emit, +// dharma_field. Single-turn LLM calls are allowed. +// "utility" — default. No DHARMA, no LLM. Pure compute + I/O. +// +// The compiler-level rule is structural: the binary either CAN or CANNOT +// emit the call. There is no runtime check, no opt-in, no override. + +fn cap_record_violation(kind: String, fn_name: String) -> Bool { + let csv: String = state_get("__cap_violations") + if str_eq(csv, "") { let csv = "," } + let entry: String = kind + ":" + fn_name + let key: String = "," + entry + "," + if str_contains(csv, key) { return true } + state_set("__cap_violations", csv + entry + ",") + return true +} + +// Self-formation primitives — the cut between CGI and service. A program +// that emits these calls IS structurally a CGI; we forbid them everywhere +// else. +fn is_self_formation_call(fn_name: String) -> Bool { + if str_eq(fn_name, "llm_call_agentic") { return true } + if str_eq(fn_name, "llm_register_tool") { return true } + if str_eq(fn_name, "dharma_emit") { return true } + if str_eq(fn_name, "dharma_field") { return true } + return false +} + +// Any DHARMA primitive — utilities have zero network presence. +fn is_dharma_call(fn_name: String) -> Bool { + if str_eq(fn_name, "dharma_connect") { return true } + if str_eq(fn_name, "dharma_send") { return true } + if str_eq(fn_name, "dharma_activate") { return true } + if str_eq(fn_name, "dharma_emit") { return true } + if str_eq(fn_name, "dharma_field") { return true } + if str_eq(fn_name, "dharma_strengthen") { return true } + if str_eq(fn_name, "dharma_relationship") { return true } + if str_eq(fn_name, "dharma_peers") { return true } + return false +} + +// Any LLM primitive — utilities have no LLM access at all. +fn is_llm_call(fn_name: String) -> Bool { + if str_eq(fn_name, "llm_call") { return true } + if str_eq(fn_name, "llm_call_system") { return true } + if str_eq(fn_name, "llm_call_agentic") { return true } + if str_eq(fn_name, "llm_vision") { return true } + if str_eq(fn_name, "llm_register_tool") { return true } + if str_eq(fn_name, "llm_models") { return true } + return false +} + +fn cap_check_call(fn_name: String) -> Bool { + let kind: String = state_get("__program_kind") + if str_eq(kind, "cgi") { return true } + if str_eq(kind, "service") { + if is_self_formation_call(fn_name) { + cap_record_violation("service", fn_name) + return false + } + return true + } + // utility (default) + if is_dharma_call(fn_name) { + cap_record_violation("utility", fn_name) + return false + } + if is_llm_call(fn_name) { + cap_record_violation("utility", fn_name) + return false + } + return true +} + +// Emit collected capability violations as #error directives. Called +// from codegen()'s entry point right after the cgi/service-block scan, +// so they appear at the very top of the generated C. +fn emit_cap_violations() -> Void { + let csv: String = state_get("__cap_violations") + if str_eq(csv, "") { return } + if str_eq(csv, ",") { return } + let n: Int = str_len(csv) + let i: Int = 1 + while i < n { + let next_comma: Int = str_index_of(str_slice(csv, i, n), ",") + if next_comma < 0 { return } + let entry: String = str_slice(csv, i, i + next_comma) + let colon: Int = str_index_of(entry, ":") + if colon > 0 { + let kind: String = str_slice(entry, 0, colon) + let fn_name: String = str_slice(entry, colon + 1, str_len(entry)) + emit_line("#error \"capability violation: '" + kind + "' programs may not call '" + fn_name + "' (self-formation primitive — only 'cgi' programs may use it)\"") + } + let i = i + next_comma + 1 + } +} + fn add_int_name(name: String) -> Bool { let csv: String = state_get("__int_names") if str_eq(csv, "") { csv = "," } @@ -1039,10 +1153,21 @@ fn vbd_has_restricted_call(stmts: [Map]) -> Bool { // ── Entry point ──────────────────────────────────────────────────────────────── fn codegen(stmts: [Map], source: String) -> String { - // Detect cgi blocks: at most one allowed. Emit a #error if more than one. + // Detect cgi/service blocks: at most one declarative top-level block. + // The block determines the program's CAPABILITY KIND: + // "cgi" — full self-formation. Calls all primitives. + // "service" — bounded. Cannot call self-formation primitives + // (llm_call_agentic, llm_register_tool, dharma_emit, + // dharma_field, mindlink-creation). + // "utility" — default; no DHARMA membership, no LLM, no agentic. + // Codegen enforces this with #error directives at every restricted + // call site. The capability boundary is structural: a binary either + // CAN or CANNOT do a thing, and the compiler decides at emission time. let n_top: Int = native_list_len(stmts) let cgi_count = 0 let cgi_block: Map = { "stmt": "None" } + let svc_count = 0 + let svc_block: Map = { "stmt": "None" } let ti = 0 while ti < n_top { let s = native_list_get(stmts, ti) @@ -1053,11 +1178,33 @@ fn codegen(stmts: [Map], source: String) -> String { let cgi_block = s } } + if str_eq(sk, "ServiceBlock") { + let svc_count = svc_count + 1 + if svc_count == 1 { + let svc_block = s + } + } let ti = ti + 1 } if cgi_count > 1 { emit_line("#error \"El: multiple cgi blocks in program (only one allowed)\"") } + if svc_count > 1 { + emit_line("#error \"El: multiple service blocks in program (only one allowed)\"") + } + if cgi_count >= 1 { + if svc_count >= 1 { + emit_line("#error \"El: program declares both cgi and service blocks (mutually exclusive — pick one)\"") + } + } + // Stash the program kind so cg_expr's Call branch can enforce + // per-kind capability restrictions on every emitted call. + let kind: String = "utility" + if cgi_count >= 1 { let kind = "cgi" } + if svc_count >= 1 { let kind = "service" } + state_set("__program_kind", kind) + // Clear capability-violation accumulator from any prior compile. + state_set("__cap_violations", "") // Preamble emit_line("#include ") @@ -1132,6 +1279,12 @@ fn codegen(stmts: [Map], source: String) -> String { emit_line("}") emit_blank() + // Emit any accumulated capability-violation #error directives. cc + // will fail on the first one and surface the message; placement at + // the bottom is fine — preprocessor errors halt the build wherever + // they appear. + emit_cap_violations() + // Return empty string — output was streamed via println "" } diff --git a/el-compiler/src/lexer.el b/el-compiler/src/lexer.el index c7e3c16..6f7f742 100644 --- a/el-compiler/src/lexer.el +++ b/el-compiler/src/lexer.el @@ -141,6 +141,7 @@ fn keyword_kind(word: String) -> String { if word == "true" { return "Bool" } if word == "false" { return "Bool" } if word == "cgi" { return "Cgi" } + if word == "service" { return "Service" } if word == "manager" { return "Manager" } if word == "engine" { return "Engine" } if word == "accessor" { return "Accessor" } diff --git a/el-compiler/src/parser.el b/el-compiler/src/parser.el index d194392..ca8a904 100644 --- a/el-compiler/src/parser.el +++ b/el-compiler/src/parser.el @@ -825,6 +825,55 @@ fn parse_stmt(tokens: [Map], pos: Int) -> Map { }, p) } + // service block: service "name" { sponsor: "...", domain: "...", ... } + // + // A `service` declaration restricts the program's capabilities at + // compile time: services CANNOT call self-formation primitives + // (llm_call_agentic, llm_register_tool, dharma_emit, dharma_field, + // mindlink-creation). Codegen enforces this with #error directives. + if k == "Service" { + let p = pos + 1 + let name = tok_value(tokens, p) + let p = p + 1 + let p = expect(tokens, p, "LBrace") + let sponsor = "" + let domain = "" + let running = true + while running { + let k2 = tok_kind(tokens, p) + if k2 == "RBrace" { + let running = false + } else { + if k2 == "Eof" { + let running = false + } else { + let fname = tok_value(tokens, p) + let p = p + 1 + let p = expect(tokens, p, "Colon") + let fval = tok_value(tokens, p) + let p = p + 1 + if str_eq(fname, "sponsor") { + let sponsor = fval + } + if str_eq(fname, "domain") { + let domain = fval + } + let k3 = tok_kind(tokens, p) + if k3 == "Comma" { + let p = p + 1 + } + } + } + } + let p = expect(tokens, p, "RBrace") + return make_result({ + "stmt": "ServiceBlock", + "name": name, + "sponsor": sponsor, + "domain": domain + }, p) + } + // bare expression or if/match statement let r = parse_expr(tokens, pos) let val = r["node"] diff --git a/elc-combined.el b/elc-combined.el index 7886ad6..bf039c6 100644 --- a/elc-combined.el +++ b/elc-combined.el @@ -142,6 +142,7 @@ fn keyword_kind(word: String) -> String { if word == "true" { return "Bool" } if word == "false" { return "Bool" } if word == "cgi" { return "Cgi" } + if word == "service" { return "Service" } if word == "manager" { return "Manager" } if word == "engine" { return "Engine" } if word == "accessor" { return "Accessor" } @@ -1361,6 +1362,55 @@ fn parse_stmt(tokens: [Map], pos: Int) -> Map { }, p) } + // service block: service "name" { sponsor: "...", domain: "...", ... } + // + // A `service` declaration restricts the program's capabilities at + // compile time: services CANNOT call self-formation primitives + // (llm_call_agentic, llm_register_tool, dharma_emit, dharma_field, + // mindlink-creation). Codegen enforces this with #error directives. + if k == "Service" { + let p = pos + 1 + let name = tok_value(tokens, p) + let p = p + 1 + let p = expect(tokens, p, "LBrace") + let sponsor = "" + let domain = "" + let running = true + while running { + let k2 = tok_kind(tokens, p) + if k2 == "RBrace" { + let running = false + } else { + if k2 == "Eof" { + let running = false + } else { + let fname = tok_value(tokens, p) + let p = p + 1 + let p = expect(tokens, p, "Colon") + let fval = tok_value(tokens, p) + let p = p + 1 + if str_eq(fname, "sponsor") { + let sponsor = fval + } + if str_eq(fname, "domain") { + let domain = fval + } + let k3 = tok_kind(tokens, p) + if k3 == "Comma" { + let p = p + 1 + } + } + } + } + let p = expect(tokens, p, "RBrace") + return make_result({ + "stmt": "ServiceBlock", + "name": name, + "sponsor": sponsor, + "domain": domain + }, p) + } + // bare expression or if/match statement let r = parse_expr(tokens, pos) let val = r["node"] @@ -1745,6 +1795,12 @@ fn cg_expr(expr: Map) -> String { if func_kind == "Ident" { let fn_name: String = func["name"] + // Capability-kind enforcement: services can't call + // self-formation primitives; utilities can't call any + // DHARMA or LLM primitives. cap_check_call records + // violations to be emitted as #error directives at the + // top of the generated C, so cc fails with a clear msg. + cap_check_call(fn_name) return fn_name + "(" + args_c + ")" } @@ -2207,6 +2263,114 @@ fn is_int_call(call_expr: Map) -> Bool { return false } +// ── Capability-kind enforcement ────────────────────────────────────────────── +// +// A program's top-level block (cgi / service / none) determines which +// runtime primitives it may call. The compiler records violations in +// process state during cg_expr's Call emission; codegen's entry point +// then emits #error directives at the top of the generated C so the +// downstream cc step fails with a clear message. +// +// Capability tiers: +// "cgi" — full self-formation. All primitives. +// "service" — bounded. Cannot call self-formation primitives: +// llm_call_agentic, llm_register_tool, dharma_emit, +// dharma_field. Single-turn LLM calls are allowed. +// "utility" — default. No DHARMA, no LLM. Pure compute + I/O. +// +// The compiler-level rule is structural: the binary either CAN or CANNOT +// emit the call. There is no runtime check, no opt-in, no override. + +fn cap_record_violation(kind: String, fn_name: String) -> Bool { + let csv: String = state_get("__cap_violations") + if str_eq(csv, "") { let csv = "," } + let entry: String = kind + ":" + fn_name + let key: String = "," + entry + "," + if str_contains(csv, key) { return true } + state_set("__cap_violations", csv + entry + ",") + return true +} + +// Self-formation primitives — the cut between CGI and service. A program +// that emits these calls IS structurally a CGI; we forbid them everywhere +// else. +fn is_self_formation_call(fn_name: String) -> Bool { + if str_eq(fn_name, "llm_call_agentic") { return true } + if str_eq(fn_name, "llm_register_tool") { return true } + if str_eq(fn_name, "dharma_emit") { return true } + if str_eq(fn_name, "dharma_field") { return true } + return false +} + +// Any DHARMA primitive — utilities have zero network presence. +fn is_dharma_call(fn_name: String) -> Bool { + if str_eq(fn_name, "dharma_connect") { return true } + if str_eq(fn_name, "dharma_send") { return true } + if str_eq(fn_name, "dharma_activate") { return true } + if str_eq(fn_name, "dharma_emit") { return true } + if str_eq(fn_name, "dharma_field") { return true } + if str_eq(fn_name, "dharma_strengthen") { return true } + if str_eq(fn_name, "dharma_relationship") { return true } + if str_eq(fn_name, "dharma_peers") { return true } + return false +} + +// Any LLM primitive — utilities have no LLM access at all. +fn is_llm_call(fn_name: String) -> Bool { + if str_eq(fn_name, "llm_call") { return true } + if str_eq(fn_name, "llm_call_system") { return true } + if str_eq(fn_name, "llm_call_agentic") { return true } + if str_eq(fn_name, "llm_vision") { return true } + if str_eq(fn_name, "llm_register_tool") { return true } + if str_eq(fn_name, "llm_models") { return true } + return false +} + +fn cap_check_call(fn_name: String) -> Bool { + let kind: String = state_get("__program_kind") + if str_eq(kind, "cgi") { return true } + if str_eq(kind, "service") { + if is_self_formation_call(fn_name) { + cap_record_violation("service", fn_name) + return false + } + return true + } + // utility (default) + if is_dharma_call(fn_name) { + cap_record_violation("utility", fn_name) + return false + } + if is_llm_call(fn_name) { + cap_record_violation("utility", fn_name) + return false + } + return true +} + +// Emit collected capability violations as #error directives. Called +// from codegen()'s entry point right after the cgi/service-block scan, +// so they appear at the very top of the generated C. +fn emit_cap_violations() -> Void { + let csv: String = state_get("__cap_violations") + if str_eq(csv, "") { return } + if str_eq(csv, ",") { return } + let n: Int = str_len(csv) + let i: Int = 1 + while i < n { + let next_comma: Int = str_index_of(str_slice(csv, i, n), ",") + if next_comma < 0 { return } + let entry: String = str_slice(csv, i, i + next_comma) + let colon: Int = str_index_of(entry, ":") + if colon > 0 { + let kind: String = str_slice(entry, 0, colon) + let fn_name: String = str_slice(entry, colon + 1, str_len(entry)) + emit_line("#error \"capability violation: '" + kind + "' programs may not call '" + fn_name + "' (self-formation primitive — only 'cgi' programs may use it)\"") + } + let i = i + next_comma + 1 + } +} + fn add_int_name(name: String) -> Bool { let csv: String = state_get("__int_names") if str_eq(csv, "") { csv = "," } @@ -2439,10 +2603,21 @@ fn vbd_has_restricted_call(stmts: [Map]) -> Bool { // ── Entry point ──────────────────────────────────────────────────────────────── fn codegen(stmts: [Map], source: String) -> String { - // Detect cgi blocks: at most one allowed. Emit a #error if more than one. + // Detect cgi/service blocks: at most one declarative top-level block. + // The block determines the program's CAPABILITY KIND: + // "cgi" — full self-formation. Calls all primitives. + // "service" — bounded. Cannot call self-formation primitives + // (llm_call_agentic, llm_register_tool, dharma_emit, + // dharma_field, mindlink-creation). + // "utility" — default; no DHARMA membership, no LLM, no agentic. + // Codegen enforces this with #error directives at every restricted + // call site. The capability boundary is structural: a binary either + // CAN or CANNOT do a thing, and the compiler decides at emission time. let n_top: Int = native_list_len(stmts) let cgi_count = 0 let cgi_block: Map = { "stmt": "None" } + let svc_count = 0 + let svc_block: Map = { "stmt": "None" } let ti = 0 while ti < n_top { let s = native_list_get(stmts, ti) @@ -2453,11 +2628,33 @@ fn codegen(stmts: [Map], source: String) -> String { let cgi_block = s } } + if str_eq(sk, "ServiceBlock") { + let svc_count = svc_count + 1 + if svc_count == 1 { + let svc_block = s + } + } let ti = ti + 1 } if cgi_count > 1 { emit_line("#error \"El: multiple cgi blocks in program (only one allowed)\"") } + if svc_count > 1 { + emit_line("#error \"El: multiple service blocks in program (only one allowed)\"") + } + if cgi_count >= 1 { + if svc_count >= 1 { + emit_line("#error \"El: program declares both cgi and service blocks (mutually exclusive — pick one)\"") + } + } + // Stash the program kind so cg_expr's Call branch can enforce + // per-kind capability restrictions on every emitted call. + let kind: String = "utility" + if cgi_count >= 1 { let kind = "cgi" } + if svc_count >= 1 { let kind = "service" } + state_set("__program_kind", kind) + // Clear capability-violation accumulator from any prior compile. + state_set("__cap_violations", "") // Preamble emit_line("#include ") @@ -2532,6 +2729,12 @@ fn codegen(stmts: [Map], source: String) -> String { emit_line("}") emit_blank() + // Emit any accumulated capability-violation #error directives. cc + // will fail on the first one and surface the message; placement at + // the bottom is fine — preprocessor errors halt the build wherever + // they appear. + emit_cap_violations() + // Return empty string — output was streamed via println "" }