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:
Vendored
BIN
Binary file not shown.
Vendored
+209
-1
@@ -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;
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
+154
-1
@@ -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
|
||||
""
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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
@@ -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
|
||||
""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user