diff --git a/lang/swarm/containment.el b/lang/swarm/containment.el index 75bd3ee..c57e2c8 100644 --- a/lang/swarm/containment.el +++ b/lang/swarm/containment.el @@ -21,8 +21,19 @@ // ── Token minting ──────────────────────────────────────────────────────────── -// containment_coordinator_token — the token a coordinator holds. Depth 0. -// Only a coordinator token may open a swarm. +// CAPABILITIES. A scope token carries a `caps` set — the authority it holds. +// This is an AUTHORITY gate, not a health gate: capability is decided at mint +// time and cannot be acquired at runtime. Engram-WRITE (op_write/op_relate/ +// op_supersede -> POST /api/nodes, /api/edges, DELETE) and dharma_emit are +// @manager-ONLY capabilities — exactly the VBD rule that only the orchestrator +// mutates global state. The orchestrator's token carries them; a worker's token +// NEVER does. A worker is therefore STRUCTURALLY UNABLE to mutate global engram +// state, regardless of engram health. +fn cap_orchestrator() -> String { return "engram:read,engram:write,dharma:emit,state:write" } +fn cap_worker() -> String { return "engram:read" } + +// containment_coordinator_token — the token the orchestrator (@manager) holds. +// Depth 0. Carries the engram-WRITE + dharma-emit capabilities (@manager-only). fn containment_coordinator_token(corr_id: String) -> String { let kv: [String] = el_list_empty() let kv = el_list_append(kv, "kind") @@ -33,11 +44,15 @@ fn containment_coordinator_token(corr_id: String) -> String { let kv = el_list_append(kv, "") let kv = el_list_append(kv, "depth") let kv = el_list_append(kv, "0") + let kv = el_list_append(kv, "caps") + let kv = el_list_append(kv, cap_orchestrator()) return json_build_object(kv) } // containment_worker_token — the token stamped into a worker's envelope. Depth 1. -// A worker token is a closed boundary: holding it forbids opening/joining swarms. +// A closed boundary: forbids opening/joining swarms AND carries ONLY the +// engram:READ capability — no engram:write, no dharma:emit. Read-only against the +// full engram; may write only its own local geometry (its returned result). fn containment_worker_token(corr_id: String, worker_id: String) -> String { let kv: [String] = el_list_empty() let kv = el_list_append(kv, "kind") @@ -48,9 +63,16 @@ fn containment_worker_token(corr_id: String, worker_id: String) -> String { let kv = el_list_append(kv, worker_id) let kv = el_list_append(kv, "depth") let kv = el_list_append(kv, "1") + let kv = el_list_append(kv, "caps") + let kv = el_list_append(kv, cap_worker()) return json_build_object(kv) } +// containment_has_cap — does this token carry capability `cap`? +fn containment_has_cap(token: String, cap: String) -> Bool { + return str_contains(json_get_string(token, "caps"), cap) +} + // ── Rule checks (return "" on allow, or a rejection reason string) ─────────── // containment_check_open — may the holder of `token` OPEN a new swarm? @@ -97,6 +119,28 @@ fn containment_check_lateral(from_token: String, to_worker_id: String) -> String return "" } +// containment_check_engram_write — RULE 4: only a token carrying the +// engram:write capability (the orchestrator's) may mutate global engram state. +// A worker token (engram:read only) is REJECTED — the authority gate. Reuses the +// exact scope-token mechanism as Rule 2's open-denial. Returns "" on allow, or a +// rejection reason. This is an AUTHORITY gate: it does not consult engram health. +fn containment_check_engram_write(token: String, op: String) -> String { + if containment_has_cap(token, "engram:write") { + return "" + } + return "CONTAINMENT rule 4: engram-write is @manager-only — a worker is read-only against the engram and may not mutate global state (op=" + op + " kind=" + json_get_string(token, "kind") + " worker=" + json_get_string(token, "worker") + " caps=" + json_get_string(token, "caps") + ")" +} + +// containment_check_dharma_emit — the same @manager-only rule for dharma_emit, +// grounding Rule 4 in VBD: global-state mutations (engram-write, dharma-emit) are +// orchestrator-only, checked by the one capability mechanism. +fn containment_check_dharma_emit(token: String) -> String { + if containment_has_cap(token, "dharma:emit") { + return "" + } + return "CONTAINMENT rule 4: dharma_emit is @manager-only (kind=" + json_get_string(token, "kind") + ")" +} + // ── Enforcement helpers ────────────────────────────────────────────────────── // containment_allows_open — Bool convenience over containment_check_open. @@ -121,3 +165,17 @@ fn containment_guard_open(token: String, corr_id: String) -> String { worktrack_append("containment.violation", corr_id, "open", p) return reason } + +// containment_guard_engram_write — assert a token may mutate global engram state +// (Rule 4). Returns "" if allowed; otherwise journals a containment.violation and +// returns the reason. The write path MUST abort on a non-empty return. +fn containment_guard_engram_write(token: String, corr_id: String, op: String) -> String { + let reason: String = containment_check_engram_write(token, op) + if str_eq(reason, "") { + return "" + } + let p0: String = json_set_str("{}", "reason", reason) + let p1: String = json_set_str(p0, "op", op) + worktrack_append("containment.violation", corr_id, "engram-write", p1) + return reason +} diff --git a/lang/swarm/reshape_surface.el b/lang/swarm/reshape_surface.el index 72c1979..7404010 100644 --- a/lang/swarm/reshape_surface.el +++ b/lang/swarm/reshape_surface.el @@ -64,6 +64,34 @@ fn op_attend(node: String, observer: String) -> String { return http_post_json(engram_url() + "/api/attend", body) } +fn identity_typed(t: String) -> Bool { + if str_eq(t, "self") { return true } + if str_eq(t, "values") { return true } + return false +} +fn type_to_node_type(t: String) -> String { + if str_eq(t, "knowledge") { return "Knowledge" } + if str_eq(t, "artifact") { return "Artifact" } + if str_eq(t, "backlog") { return "WorkItem" } + if str_eq(t, "process") { return "Process" } + if str_eq(t, "state") { return "InternalStateEvent" } + return "Memory" +} + +// write — add a node (POST /api/nodes). Identity types refused. This is a +// global-engram MUTATION — @manager-only (Rule 4); never called on a worker path. +// (Reshape's op_write, with json_escape -> the available json_escape_string.) +fn op_write(content: String, typ: String, importance: Float) -> String { + if str_eq(content, "") { return "{\"error\":\"write: content required\"}" } + if identity_typed(typ) { + return "{\"error\":\"write type=" + typ + " is write-protected -> intentional-cultivation\"}" + } + let body: String = "{\"_auth\":\"" + engram_key() + "\",\"content\":\"" + json_escape_string(content) + + "\",\"node_type\":\"" + type_to_node_type(typ) + "\",\"tier\":\"Working\",\"importance\":" + + float_to_str(importance) + "}" + return http_post_json(engram_url() + "/api/nodes", body) +} + // learn — the reflexive correspondence-beat: calibrate the steering-prior. // (POST — needs a write-healthy clone.) fn op_learn(seeds: String, faculty: String) -> String { diff --git a/lang/swarm/swarm.el b/lang/swarm/swarm.el index a676ede..3f74297 100644 --- a/lang/swarm/swarm.el +++ b/lang/swarm/swarm.el @@ -300,6 +300,28 @@ fn swarm_converge(strategy: String, results: [String]) -> String { return swarm_converge_collect(results) } +// ── the ONLY global-engram write path (Rule 4, @manager-only) ──────────────── +// +// Every engram mutation flows through here and is gated by the caller's token +// capability. Only the orchestrator's token carries engram:write, so a worker +// (engram:read only) calling this is DENIED by capability before any HTTP is +// issued — structurally unable to mutate global engram state, regardless of +// engram health. This is the curated-merge write: the orchestrator committing +// the geometry it approved. Workers never reach a successful branch here. +fn swarm_engram_write(token: String, corr_id: String, content: String, typ: String, importance: Float) -> String { + let deny: String = containment_guard_engram_write(token, corr_id, "engram.write") + if str_eq(deny, "") { + // authorized (orchestrator) — perform the write + let res: String = op_write(content, typ, importance) + let new_id: String = json_get_string(res, "id") + let cp: String = json_set_str("{}", "node_id", new_id) + worktrack_append("swarm.committed", corr_id, "orchestrator", cp) + return res + } + // denied by capability — return the rejection, no engram mutation performed + return json_set_str("{}", "denied", deny) +} + // ── the coordinator: fan out -> track -> converge ──────────────────────────── // // blueprint : task blueprint name run by every worker @@ -426,6 +448,17 @@ fn swarm_run(blueprint: String, knowledge_refs: String, inputs_json: String, con let dp: String = json_set_str("{}", "strategy", strategy) worktrack_append("swarm.completed", corr_id, corr_id, dp) + // ── curated merge = the ONLY engram write path (Rule 4) ── + // With "commit":"1", the ORCHESTRATOR (its token carries engram:write) + // commits the approved merged geometry back to the engram. This is the + // single writer. Workers returned geometry; only the orchestrator writes. + let commit_id: String = "" + if str_eq(json_get_string(config_json, "commit"), "1") { + let orch_token: String = containment_coordinator_token(corr_id) + let cres: String = swarm_engram_write(orch_token, corr_id, "swarm-merge " + corr_id + " :: " + merged, "memory", 0.5) + let commit_id = json_get_string(cres, "id") + } + let rep2: String = worktrack_swarm_report(corr_id) let ok2: [String] = el_list_empty() let ok2 = el_list_append(ok2, "corr_id") @@ -435,7 +468,8 @@ fn swarm_run(blueprint: String, knowledge_refs: String, inputs_json: String, con let out1: String = json_build_object(ok2) let out2: String = json_set(out1, "report", rep2) let out3: String = json_set(out2, "merged", merged) - return json_set(out3, "telemetry", telemetry) + let out4: String = json_set(out3, "telemetry", telemetry) + return json_set_str(out4, "committed_node", commit_id) } // ── denied: caller was a worker trying to open a swarm (Rule 2) ── let dkv: [String] = el_list_empty() diff --git a/lang/swarm/tests/harness_real_cognition.el b/lang/swarm/tests/harness_real_cognition.el index 477a1d7..ca98691 100644 --- a/lang/swarm/tests/harness_real_cognition.el +++ b/lang/swarm/tests/harness_real_cognition.el @@ -76,6 +76,39 @@ fn main() -> Int { let completed: Int = worktrack_count_kind(corr, "worker.completed") let fails = ok("work-tracking: 8 started + 8 completed", (started == 8) && (completed == 8), fails) + // ── G) RULE 4 — engram-write is @manager-ONLY (authority gate) ── + // A worker token (engram:read only) is STRUCTURALLY denied any engram write. + let worker_tok: String = containment_worker_token(corr, corr + "/worker-1") + let orch_tok: String = containment_coordinator_token(corr) + let fails = ok("worker token carries engram:read", containment_has_cap(worker_tok, "engram:read"), fails) + let fails = ok("worker token does NOT carry engram:write", !containment_has_cap(worker_tok, "engram:write"), fails) + let fails = ok("orchestrator token carries engram:write", containment_has_cap(orch_tok, "engram:write"), fails) + // a worker attempting an engram write is DENIED BY CAPABILITY (no HTTP issued) + let wdeny: String = swarm_engram_write(worker_tok, corr, "worker tries to mutate global state", "memory", 0.5) + let denied_reason: String = json_get_string(wdeny, "denied") + let fails = ok("worker engram-write DENIED by capability (Rule 4)", str_contains(denied_reason, "rule 4"), fails) + let fails = ok("denied worker write performed NO engram mutation (no node id)", str_eq(json_get_string(wdeny, "id"), ""), fails) + let fails = ok("Rule-4 violation journalled", worktrack_count_kind(corr, "containment.violation") >= 1, fails) + // the orchestrator passes the capability gate (sole authorized writer) + let odeny: String = containment_check_engram_write(orch_tok, "engram.write") + let fails = ok("orchestrator PASSES the engram-write capability gate (sole writer)", str_eq(odeny, ""), fails) + + // ── H) curated merge = the only write path (orchestrator commits) ── + // The AUTHORITY gate above is already proven (worker denied, orchestrator + // authorized) WITHOUT issuing a write. The actual persisting commit exercises + // the engram write path, which needs the gate-1 write-healthy clone — so it + // runs only under SWARM_WRITE_HEALTHY=1 (else it would hit the known daemon + // write-crash). Authority != health: the gate holds either way. + if str_eq(env("SWARM_WRITE_HEALTHY"), "1") { + let cfg_commit: String = "{\"concurrency\":\"4\",\"strategy\":\"reduce\",\"min_success_ratio\":\"1.0\",\"commit\":\"1\"}" + let rc: String = swarm_run("cognize", refs, anchors, cfg_commit) + let committed: String = json_get_string(rc, "committed_node") + let fails2: Int = ok("orchestrator (sole writer) committed the merge to the engram", !str_eq(committed, ""), fails) + let fails = fails2 + } else { + print(" note curated-merge commit deferred to the gate-1 write-healthy clone (set SWARM_WRITE_HEALTHY=1); authority gate already proven above") + } + print("") if fails == 0 { print("REAL-COGNITION SWARM GREEN — Neuron thinking in parallel over its own geometry.")