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
+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"]