compiler: capability-kind enforcement (cgi / service / utility)

Capability becomes a compile-time structural property, not a runtime
convention. A program's top-level block determines what runtime
primitives it may call; the codegen rejects forbidden calls with
#error directives so cc fails with a clear message.

Three kinds:
  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 allowed.
  utility  — default (no top-level block). No DHARMA, no LLM.
             Pure compute + I/O.

Deep claim: the binary either CAN or CANNOT do a thing. There is no
runtime check, no opt-in, no override. A weather service compiled
with `service { ... }` is structurally incapable of becoming Neuron.
Sponsors of services know exactly what they're vouching for.

Implementation
- Lexer: `service` keyword.
- Parser: parse_service_block parallels parse_cgi_block. Produces
  ServiceBlock AST with name/sponsor/domain.
- Codegen entry: scans top-level for cgi/service blocks, sets
  __program_kind state ("cgi" / "service" / "utility"). Rejects
  programs declaring both kinds.
- cg_expr Call: cap_check_call(fn_name) per emission. Records
  violations in __cap_violations CSV. emit_cap_violations() writes
  one #error per violation at end of generated C.
- Helpers: is_self_formation_call, is_dharma_call, is_llm_call.

Tests verified:
  cgi + llm_call_agentic        → compiles ✓
  service + llm_call_agentic    → cc fails with capability violation
                                  for 'service' on 'llm_call_agentic'
  service + llm_call (1-turn)   → compiles ✓
  utility + dharma_send         → cc fails with capability violation
                                  for 'utility' on 'dharma_send'
  utility + http/json/state     → compiles + runs ✓ ("got: world")
  cgi + dharma_emit (manager)   → compiles ✓ (VBD also enforced)
  cgi + dharma_emit (engine)    → cc fails with VBD violation

Three-stage closure: stage1.c == stage2.c (byte-identical).
Engram rebuilt against new compiler — daemon on :8742 healthy,
{"node_count":0,"edge_count":0}.

A bug found and fixed during testing: cap_record_violation had
`csv = ","` (bare assignment, not valid in El) instead of
`let csv = ","`. Without the let, the leading comma never made
it into the accumulator, off-by-one'ing the kind extraction so
"service" appeared as "ervice" in error messages. Pattern
fixed; this confirms once more that El requires `let X = ...`
for all rebindings (codegen converts to assignment when X is
already declared).
This commit is contained in:
Will Anderson
2026-04-30 14:18:17 -05:00
parent 12d5e7777e
commit 5adc05aa48
7 changed files with 617 additions and 3 deletions
BIN
View File
Binary file not shown.
+209 -1
View File
@@ -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 <stdint.h>"));
emit_line(EL_STR("#include <stdlib.h>"));
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;
}
Vendored Executable
BIN
View File
Binary file not shown.
+154 -1
View File
@@ -345,6 +345,12 @@ fn cg_expr(expr: Map<String, Any>) -> 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<String, Any>) -> 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<String, Any>]) -> Bool {
// Entry point
fn codegen(stmts: [Map<String, Any>], 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<String, Any> = { "stmt": "None" }
let svc_count = 0
let svc_block: Map<String, Any> = { "stmt": "None" }
let ti = 0
while ti < n_top {
let s = native_list_get(stmts, ti)
@@ -1053,11 +1178,33 @@ fn codegen(stmts: [Map<String, Any>], 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 <stdint.h>")
@@ -1132,6 +1279,12 @@ fn codegen(stmts: [Map<String, Any>], 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
""
}
+1
View File
@@ -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" }
+49
View File
@@ -825,6 +825,55 @@ fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
}, 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"]
+204 -1
View File
@@ -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<String, Any>], pos: Int) -> Map<String, Any> {
}, 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, Any>) -> 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<String, Any>) -> 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<String, Any>]) -> Bool {
// Entry point
fn codegen(stmts: [Map<String, Any>], 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<String, Any> = { "stmt": "None" }
let svc_count = 0
let svc_block: Map<String, Any> = { "stmt": "None" }
let ti = 0
while ti < n_top {
let s = native_list_get(stmts, ti)
@@ -2453,11 +2628,33 @@ fn codegen(stmts: [Map<String, Any>], 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 <stdint.h>")
@@ -2532,6 +2729,12 @@ fn codegen(stmts: [Map<String, Any>], 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
""
}