From d2d08d2a37d02030270dc9efe390f6f3a185b0f6 Mon Sep 17 00:00:00 2001 From: Alfonso Sastre Date: Sat, 6 Jun 2026 09:23:23 +0200 Subject: [PATCH 1/4] feat: update agents, tools, permissions and web UI - lex.toml: switch to git deps - src/agents/*: build, explore, plan, refactor, review, spec, test agents updated - src/permissions/rules.lex, manifests.lex: permission rules and manifests - src/tools/index.lex, os_check.lex: tool index and OS check tool - src/server/web.lex: web server - src/web/index.html: updated UI Co-Authored-By: Claude Sonnet 4.6 --- lex.toml | 12 ++-- src/agents/build.lex | 4 +- src/agents/explore.lex | 2 +- src/agents/plan.lex | 4 +- src/agents/refactor.lex | 4 +- src/agents/review.lex | 4 +- src/agents/spec_agent.lex | 4 +- src/agents/test_agent.lex | 2 +- src/permissions/manifests.lex | 97 ++++++++++++++++++++++++++++ src/permissions/rules.lex | 2 +- src/tools/index.lex | 4 +- src/tools/os_check.lex | 117 ++++++++++++++++++++++++++++++++++ src/web/index.html | 2 +- 13 files changed, 238 insertions(+), 20 deletions(-) create mode 100644 src/permissions/manifests.lex create mode 100644 src/tools/os_check.lex diff --git a/lex.toml b/lex.toml index 4262705..dfdfaa2 100644 --- a/lex.toml +++ b/lex.toml @@ -4,11 +4,13 @@ version = "0.1.0" lex = "0.9.7" [dependencies] -"lex-llm" = { path = "../lex-llm" } -"lex-agent" = { path = "../lex-agent" } -"lex-trail" = { path = "../lex-trail" } -"lex-spec" = { path = "../lex-spec" } -"lex-schema" = { path = "../lex-schema" } +lex-llm = { git = "https://github.com/alpibrusl/lex-llm" } +lex-agent = { git = "https://github.com/alpibrusl/lex-agent" } +lex-trail = { git = "https://github.com/alpibrusl/lex-trail" } +lex-spec = { git = "https://github.com/alpibrusl/lex-spec" } +lex-schema = { git = "https://github.com/alpibrusl/lex-schema" } +lex-os-manifest = { git = "https://github.com/alpibrusl/lex-os-manifest" } +lex-web = { git = "https://github.com/alpibrusl/lex-web" } [bin] name = "lex-code" diff --git a/src/agents/build.lex b/src/agents/build.lex index e0f8419..917350c 100644 --- a/src/agents/build.lex +++ b/src/agents/build.lex @@ -20,7 +20,7 @@ fn agent() -> [env] ag.AgentDef { } fn openai_agent() -> [env] ag.AgentDef { - let base := { name: "build", goal: bpo.system(), model: prov.gpt55(), provider: providers.openai(), tools: tools.all_tools(), options: { temperature: None, top_p: None, max_steps: Some(50), max_tokens: None }, permission_spec: None } + let base := { name: "build", goal: bpo.system(), model: prov.gpt4o(), provider: providers.openai(), tools: tools.all_tools(), options: { temperature: None, top_p: None, max_steps: Some(50), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.build_permission()) } @@ -30,7 +30,7 @@ fn mistral_agent() -> [env] ag.AgentDef { } fn ollama_agent() -> [env] ag.AgentDef { - let base := { name: "build", goal: bpo.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: list.concat(tools.standard_tools(), tools.lex_cli_tools()), options: { temperature: None, top_p: None, max_steps: Some(10), max_tokens: None }, permission_spec: None } + let base := { name: "build", goal: bpo.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: [], options: { temperature: None, top_p: None, max_steps: Some(3), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.build_permission()) } diff --git a/src/agents/explore.lex b/src/agents/explore.lex index be3994c..13fb5f2 100644 --- a/src/agents/explore.lex +++ b/src/agents/explore.lex @@ -21,7 +21,7 @@ fn mistral_agent() -> [env] ag.AgentDef { } fn ollama_agent() -> [env] ag.AgentDef { - let base := { name: "explore", goal: ep.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: tools.tools_for_spec(rules.explore_permission()), options: { temperature: None, top_p: None, max_steps: Some(20), max_tokens: None }, permission_spec: None } + let base := { name: "explore", goal: ep.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: [], options: { temperature: None, top_p: None, max_steps: Some(3), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.explore_permission()) } diff --git a/src/agents/plan.lex b/src/agents/plan.lex index d516ceb..8a74724 100644 --- a/src/agents/plan.lex +++ b/src/agents/plan.lex @@ -21,7 +21,7 @@ fn mistral_agent() -> [env] ag.AgentDef { } fn ollama_agent() -> [env] ag.AgentDef { - let base := { name: "plan", goal: pp.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: tools.tools_for_spec(rules.plan_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } + let base := { name: "plan", goal: pp.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: [], options: { temperature: None, top_p: None, max_steps: Some(3), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.plan_permission()) } @@ -31,7 +31,7 @@ fn vllm_agent() -> [env] ag.AgentDef { } fn openai_agent() -> [env] ag.AgentDef { - let base := { name: "plan", goal: pp.system(), model: prov.gpt55(), provider: providers.openai(), tools: tools.tools_for_spec(rules.plan_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } + let base := { name: "plan", goal: pp.system(), model: prov.gpt4o(), provider: providers.openai(), tools: tools.tools_for_spec(rules.plan_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.plan_permission()) } diff --git a/src/agents/refactor.lex b/src/agents/refactor.lex index 1bd6654..f41f3c7 100644 --- a/src/agents/refactor.lex +++ b/src/agents/refactor.lex @@ -21,7 +21,7 @@ fn mistral_agent() -> [env] ag.AgentDef { } fn ollama_agent() -> [env] ag.AgentDef { - let base := { name: "refactor", goal: rp.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: tools.tools_for_spec(rules.refactor_permission()), options: { temperature: None, top_p: None, max_steps: Some(40), max_tokens: None }, permission_spec: None } + let base := { name: "refactor", goal: rp.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: [], options: { temperature: None, top_p: None, max_steps: Some(3), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.refactor_permission()) } @@ -31,7 +31,7 @@ fn vllm_agent() -> [env] ag.AgentDef { } fn openai_agent() -> [env] ag.AgentDef { - let base := { name: "refactor", goal: rp.system(), model: prov.gpt55(), provider: providers.openai(), tools: tools.tools_for_spec(rules.refactor_permission()), options: { temperature: None, top_p: None, max_steps: Some(40), max_tokens: None }, permission_spec: None } + let base := { name: "refactor", goal: rp.system(), model: prov.gpt4o(), provider: providers.openai(), tools: tools.tools_for_spec(rules.refactor_permission()), options: { temperature: None, top_p: None, max_steps: Some(40), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.refactor_permission()) } diff --git a/src/agents/review.lex b/src/agents/review.lex index 8df137c..bfb7ed0 100644 --- a/src/agents/review.lex +++ b/src/agents/review.lex @@ -21,7 +21,7 @@ fn mistral_agent() -> [env] ag.AgentDef { } fn ollama_agent() -> [env] ag.AgentDef { - let base := { name: "review", goal: rvp.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: tools.tools_for_spec(rules.review_permission()), options: { temperature: None, top_p: None, max_steps: Some(25), max_tokens: None }, permission_spec: None } + let base := { name: "review", goal: rvp.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: [], options: { temperature: None, top_p: None, max_steps: Some(3), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.review_permission()) } @@ -31,7 +31,7 @@ fn vllm_agent() -> [env] ag.AgentDef { } fn openai_agent() -> [env] ag.AgentDef { - let base := { name: "review", goal: rvp.system(), model: prov.gpt55(), provider: providers.openai(), tools: tools.tools_for_spec(rules.review_permission()), options: { temperature: None, top_p: None, max_steps: Some(25), max_tokens: None }, permission_spec: None } + let base := { name: "review", goal: rvp.system(), model: prov.gpt4o(), provider: providers.openai(), tools: tools.tools_for_spec(rules.review_permission()), options: { temperature: None, top_p: None, max_steps: Some(25), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.review_permission()) } diff --git a/src/agents/spec_agent.lex b/src/agents/spec_agent.lex index ffdf6f3..6c3ce1c 100644 --- a/src/agents/spec_agent.lex +++ b/src/agents/spec_agent.lex @@ -21,7 +21,7 @@ fn mistral_agent() -> [env] ag.AgentDef { } fn ollama_agent() -> [env] ag.AgentDef { - let base := { name: "spec", goal: sp.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: tools.tools_for_spec(rules.spec_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } + let base := { name: "spec", goal: sp.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: [], options: { temperature: None, top_p: None, max_steps: Some(3), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.spec_permission()) } @@ -31,7 +31,7 @@ fn vllm_agent() -> [env] ag.AgentDef { } fn openai_agent() -> [env] ag.AgentDef { - let base := { name: "spec", goal: sp.system(), model: prov.gpt55(), provider: providers.openai(), tools: tools.tools_for_spec(rules.spec_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } + let base := { name: "spec", goal: sp.system(), model: prov.gpt4o(), provider: providers.openai(), tools: tools.tools_for_spec(rules.spec_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.spec_permission()) } diff --git a/src/agents/test_agent.lex b/src/agents/test_agent.lex index 610dd1f..50d793b 100644 --- a/src/agents/test_agent.lex +++ b/src/agents/test_agent.lex @@ -21,7 +21,7 @@ fn mistral_agent() -> [env] ag.AgentDef { } fn ollama_agent() -> [env] ag.AgentDef { - let base := { name: "test", goal: tp.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: tools.tools_for_spec(rules.test_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } + let base := { name: "test", goal: tp.system(), model: prov.ollama(providers.ollama_model()), provider: providers.ollama_local(), tools: [], options: { temperature: None, top_p: None, max_steps: Some(3), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.test_permission()) } diff --git a/src/permissions/manifests.lex b/src/permissions/manifests.lex new file mode 100644 index 0000000..94e847b --- /dev/null +++ b/src/permissions/manifests.lex @@ -0,0 +1,97 @@ +# Per-mode trust manifests for lex-os integration. +# +# Each AgentMode maps to a Grant that constrains what the mode can do +# on the three trust dimensions: filesystem, network, exec. +# +# Grant levels (from lex-types): +# filesystem: None | ReadOnly | ReadWrite | Full +# network: None | Loopback | Allowlist | Full +# exec: None | Sandboxed | Full +# +# Manifests are written to /tmp at call time and passed to `lex-os check`. + +import "std.str" as str + +import "std.int" as int + +import "std.io" as io + +fn manifest_json(goal :: Str, filesystem :: Str, network :: Str, exec_level :: Str, floor :: Str, wall :: Int, cmds :: Int, money :: Int, api_calls :: Int) -> Str { + str.join([ + "{\"goal\":{\"description\":\"", goal, "\"},", + "\"grant\":{\"filesystem\":\"", filesystem, "\",\"network\":\"", network, "\",\"exec\":\"", exec_level, "\"},", + "\"budget\":{\"wall_clock_secs\":", int.to_str(wall), ",", + "\"max_commands\":", int.to_str(cmds), ",", + "\"max_money_cents\":", int.to_str(money), ",", + "\"max_api_calls\":", int.to_str(api_calls), "},", + "\"isolation_floor\":\"", floor, "\",\"egress\":[]}" + ], "") +} + +fn explore_manifest_json() -> Str { + manifest_json("lex-code explore mode", "ReadOnly", "None", "None", "Namespace", 300, 200, 0, 50) +} + +fn plan_manifest_json() -> Str { + manifest_json("lex-code plan mode", "ReadOnly", "None", "None", "Namespace", 300, 200, 0, 50) +} + +fn review_manifest_json() -> Str { + manifest_json("lex-code review mode", "ReadOnly", "None", "None", "Namespace", 300, 200, 0, 50) +} + +fn spec_manifest_json() -> Str { + manifest_json("lex-code spec mode", "ReadWrite", "None", "None", "Namespace", 300, 200, 0, 50) +} + +fn test_manifest_json() -> Str { + manifest_json("lex-code test mode", "ReadWrite", "None", "Sandboxed", "Gvisor", 300, 200, 0, 50) +} + +fn refactor_manifest_json() -> Str { + manifest_json("lex-code refactor mode", "ReadWrite", "None", "Sandboxed", "Gvisor", 300, 200, 0, 50) +} + +fn build_manifest_json() -> Str { + manifest_json("lex-code build mode", "Full", "Allowlist", "Full", "MicroVm", 600, 500, 500, 100) +} + +fn json_for_mode(mode :: Str) -> Str { + if mode == "explore" { + explore_manifest_json() + } else { + if mode == "plan" { + plan_manifest_json() + } else { + if mode == "review" { + review_manifest_json() + } else { + if mode == "spec" { + spec_manifest_json() + } else { + if mode == "test" { + test_manifest_json() + } else { + if mode == "refactor" { + refactor_manifest_json() + } else { + build_manifest_json() + } + } + } + } + } + } +} + +fn temp_path_for_mode(mode :: Str) -> Str { + str.concat("/tmp/lex-code-manifest-", str.concat(mode, ".json")) +} + +fn write_manifest_for_mode(mode :: Str) -> [io] Result[Str, Str] { + let path := temp_path_for_mode(mode) + match io.write(path, json_for_mode(mode)) { + Ok(_) => Ok(path), + Err(e) => Err(e), + } +} diff --git a/src/permissions/rules.lex b/src/permissions/rules.lex index aa7c917..a420c36 100644 --- a/src/permissions/rules.lex +++ b/src/permissions/rules.lex @@ -53,7 +53,7 @@ fn spec_permission() -> sp.Spec { } fn refactor_permission() -> sp.Spec { - allow_tools("refactor_tools", list.concat(["read", "write", "edit", "grep", "glob", "bash", "lex_check", "lex_audit", "sigid_lookup", "effects_of", "lex_store_diff", "lex_store_apply", "lex_store_merge"], list.concat(vcs_read_names(), vcs_write_names()))) + allow_tools("refactor_tools", list.concat(["read", "write", "edit", "grep", "glob", "bash", "lex_check", "os_check", "lex_audit", "sigid_lookup", "effects_of", "lex_store_diff", "lex_store_apply", "lex_store_merge"], list.concat(vcs_read_names(), vcs_write_names()))) } fn test_permission() -> sp.Spec { diff --git a/src/tools/index.lex b/src/tools/index.lex index bf12599..43f44b7 100644 --- a/src/tools/index.lex +++ b/src/tools/index.lex @@ -14,6 +14,8 @@ import "./standard/todowrite" as todo_tool import "./lex_check" as check_tool +import "./os_check" as os_check_tool + import "./lex_audit" as audit_tool import "./lex_run" as run_tool @@ -99,7 +101,7 @@ fn vcs_tools() -> List[t.Tool] { } fn all_tools() -> List[t.Tool] { - list.concat([read_tool.tool(), write_tool.tool(), edit_tool.tool(), grep_tool.tool(), glob_tool.tool(), bash_tool.tool(), todo_tool.tool(), check_tool.tool(), audit_tool.tool(), run_tool.tool(), test_tool.tool(), spec_check_tool.tool(), spec_smt_tool.tool(), sigid_tool.tool(), attest_tool.tool(), effects_tool.tool(), store_diff_tool.tool(), store_apply_tool.tool(), store_merge_tool.tool(), guidelines_tool.tool()], vcs_tools()) + list.concat([read_tool.tool(), write_tool.tool(), edit_tool.tool(), grep_tool.tool(), glob_tool.tool(), bash_tool.tool(), todo_tool.tool(), check_tool.tool(), os_check_tool.tool(), audit_tool.tool(), run_tool.tool(), test_tool.tool(), spec_check_tool.tool(), spec_smt_tool.tool(), sigid_tool.tool(), attest_tool.tool(), effects_tool.tool(), store_diff_tool.tool(), store_apply_tool.tool(), store_merge_tool.tool(), guidelines_tool.tool()], vcs_tools()) } fn tools_for_spec(spec :: sp.Spec) -> List[t.Tool] { diff --git a/src/tools/os_check.lex b/src/tools/os_check.lex new file mode 100644 index 0000000..cc987a0 --- /dev/null +++ b/src/tools/os_check.lex @@ -0,0 +1,117 @@ +import "std.proc" as proc + +import "std.str" as str + +import "std.list" as list + +import "lex-llm/tool" as t + +import "lex-schema/json_value" as jv + +import "lex-schema/error" as e + +import "lex-schema/schema" as s + +import "./util" as util + +fn params() -> s.ModelSchema { + { title: "OsCheckArgs", description: "Check a Lex file's declared effects against the mode's trust grant", fields: [s.required_str("path", []), s.optional(s.required_str("mode", []))] } +} + +# Effects the mode forbids, derived from the lex-os grant for that mode. +# explore / plan / review — ReadOnly FS, No Net, No Exec +# spec — ReadWrite FS, No Net, No Exec +# test / refactor — ReadWrite FS, No Net, Sandboxed Exec +# build — Full FS, Allowlist Net, Full Exec (nothing forbidden) +fn forbidden_for_mode(mode :: Str) -> List[Str] { + if mode == "explore" or mode == "plan" or mode == "review" { + ["net", "proc", "fs_write"] + } else { + if mode == "spec" { + ["net", "proc"] + } else { + if mode == "test" or mode == "refactor" { + ["net"] + } else { + [] + } + } + } +} + +fn effect_in(effect :: Str, lst :: List[Str]) -> Bool { + list.fold(lst, false, fn (acc :: Bool, x :: Str) -> Bool { + acc or x == effect + }) +} + +fn violations(required :: List[Str], forbidden :: List[Str]) -> List[Str] { + list.filter(required, fn (eff :: Str) -> Bool { + effect_in(eff, forbidden) + }) +} + +fn extract_effects(check_json :: jv.Json) -> List[Str] { + match jv.get_field(check_json, "data") { + None => [], + Some(data) => match jv.get_field(data, "required_effects") { + Some(JList(items)) => list.fold(items, [], fn (acc :: List[Str], j :: jv.Json) -> List[Str] { + match j { + JStr(s) => list.concat(acc, [s]), + _ => acc, + } + }), + _ => [], + }, + } +} + +fn execute(args :: jv.Json) -> [net, io, proc] Result[jv.Json, e.Errors] { + let path := util.field_str_or(args, "path", ".") + let mode := util.field_str_or(args, "mode", "build") + match proc.spawn("lex", ["--output", "json", "check", path]) { + Err(msg) => Err(e.single("", "proc_error", msg)), + Ok(out) => if out.exit_code != 0 { + Err(e.single("", "lex_check_failed", str.concat(out.stdout, out.stderr))) + } else { + match jv.parse(out.stdout) { + Err(_) => Err(e.single("", "parse_error", "could not parse lex check output")), + Ok(parsed) => { + let required := extract_effects(parsed) + let forbidden := forbidden_for_mode(mode) + let violated := violations(required, forbidden) + if list.len(violated) == 0 { + Ok(JStr(str.concat("grant check passed [mode=", str.concat(mode, str.concat("] effects=", str.join(required, ",")))))) + } else { + Ok(JStr(str.join([ + "GRANT VIOLATION [mode=", mode, "]\n", + " forbidden effects used: ", str.join(violated, ", "), "\n", + " all required effects: ", str.join(required, ", "), "\n", + " grant allows: ", grant_summary_for_mode(mode) + ], ""))) + } + }, + } + }, + } +} + +fn grant_summary_for_mode(mode :: Str) -> Str { + if mode == "explore" or mode == "plan" or mode == "review" { + "fs=read-only net=none exec=none" + } else { + if mode == "spec" { + "fs=read-write net=none exec=none" + } else { + if mode == "test" or mode == "refactor" { + "fs=read-write net=none exec=sandboxed" + } else { + "fs=full net=allowlist exec=full" + } + } + } +} + +fn tool() -> t.Tool { + t.define("os_check", "Check a Lex file's declared effects against the mode's trust grant (lex-os integration). Run after lex_check to catch grant violations — e.g. a refactor agent must not use net effects. Returns GRANT VIOLATION with details if the file exceeds the mode's grant.", params(), execute) +} diff --git a/src/web/index.html b/src/web/index.html index 249b56d..c1fb0ca 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -24,11 +24,11 @@

lex-code

From 4254759e86819f0a8c8967e74a6307ccbc2af2e5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 6 Jun 2026 07:35:05 +0000 Subject: [PATCH 2/4] fix: close os_check mode bypass Co-authored-by: alpibrupa --- src/tools/os_check.lex | 6 +++--- src/web/index.html | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/os_check.lex b/src/tools/os_check.lex index cc987a0..20683f0 100644 --- a/src/tools/os_check.lex +++ b/src/tools/os_check.lex @@ -15,7 +15,7 @@ import "lex-schema/schema" as s import "./util" as util fn params() -> s.ModelSchema { - { title: "OsCheckArgs", description: "Check a Lex file's declared effects against the mode's trust grant", fields: [s.required_str("path", []), s.optional(s.required_str("mode", []))] } + { title: "OsCheckArgs", description: "Check a Lex file's declared effects against the refactor-mode trust grant", fields: [s.required_str("path", [])] } } # Effects the mode forbids, derived from the lex-os grant for that mode. @@ -68,7 +68,7 @@ fn extract_effects(check_json :: jv.Json) -> List[Str] { fn execute(args :: jv.Json) -> [net, io, proc] Result[jv.Json, e.Errors] { let path := util.field_str_or(args, "path", ".") - let mode := util.field_str_or(args, "mode", "build") + let mode := "refactor" match proc.spawn("lex", ["--output", "json", "check", path]) { Err(msg) => Err(e.single("", "proc_error", msg)), Ok(out) => if out.exit_code != 0 { @@ -113,5 +113,5 @@ fn grant_summary_for_mode(mode :: Str) -> Str { } fn tool() -> t.Tool { - t.define("os_check", "Check a Lex file's declared effects against the mode's trust grant (lex-os integration). Run after lex_check to catch grant violations — e.g. a refactor agent must not use net effects. Returns GRANT VIOLATION with details if the file exceeds the mode's grant.", params(), execute) + t.define("os_check", "Check a Lex file's declared effects against the refactor-mode trust grant (lex-os integration). Run after lex_check to catch grant violations — e.g. a refactor agent must not use net effects. The grant mode is fixed by the tool and cannot be overridden by model input. Returns GRANT VIOLATION with details if the file exceeds the refactor grant.", params(), execute) } diff --git a/src/web/index.html b/src/web/index.html index c1fb0ca..0a57a8e 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -24,11 +24,11 @@

lex-code

From 3433ed4e5c09f49d0c3418f194649c19e708ae0a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 6 Jun 2026 07:39:55 +0000 Subject: [PATCH 3/4] fix: fail closed on os_check grant violations Co-authored-by: alpibrupa --- src/tools/os_check.lex | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/tools/os_check.lex b/src/tools/os_check.lex index 20683f0..557a2f0 100644 --- a/src/tools/os_check.lex +++ b/src/tools/os_check.lex @@ -51,17 +51,18 @@ fn violations(required :: List[Str], forbidden :: List[Str]) -> List[Str] { }) } -fn extract_effects(check_json :: jv.Json) -> List[Str] { +fn extract_effects(check_json :: jv.Json) -> Result[List[Str], e.Errors] { match jv.get_field(check_json, "data") { - None => [], + None => Err(e.single("", "missing_field", "lex check JSON missing data.required_effects")), Some(data) => match jv.get_field(data, "required_effects") { - Some(JList(items)) => list.fold(items, [], fn (acc :: List[Str], j :: jv.Json) -> List[Str] { + Some(JList(items)) => Ok(list.fold(items, [], fn (acc :: List[Str], j :: jv.Json) -> List[Str] { match j { JStr(s) => list.concat(acc, [s]), _ => acc, } - }), - _ => [], + })), + Some(_) => Err(e.single("", "invalid_field", "lex check JSON data.required_effects must be a list")), + None => Err(e.single("", "missing_field", "lex check JSON missing data.required_effects")), }, } } @@ -77,18 +78,22 @@ fn execute(args :: jv.Json) -> [net, io, proc] Result[jv.Json, e.Errors] { match jv.parse(out.stdout) { Err(_) => Err(e.single("", "parse_error", "could not parse lex check output")), Ok(parsed) => { - let required := extract_effects(parsed) - let forbidden := forbidden_for_mode(mode) - let violated := violations(required, forbidden) - if list.len(violated) == 0 { - Ok(JStr(str.concat("grant check passed [mode=", str.concat(mode, str.concat("] effects=", str.join(required, ",")))))) - } else { - Ok(JStr(str.join([ - "GRANT VIOLATION [mode=", mode, "]\n", - " forbidden effects used: ", str.join(violated, ", "), "\n", - " all required effects: ", str.join(required, ", "), "\n", - " grant allows: ", grant_summary_for_mode(mode) - ], ""))) + match extract_effects(parsed) { + Err(errs) => Err(errs), + Ok(required) => { + let forbidden := forbidden_for_mode(mode) + let violated := violations(required, forbidden) + if list.len(violated) == 0 { + Ok(JStr(str.concat("grant check passed [mode=", str.concat(mode, str.concat("] effects=", str.join(required, ",")))))) + } else { + Err(e.single("", "grant_violation", str.join([ + "GRANT VIOLATION [mode=", mode, "]\n", + " forbidden effects used: ", str.join(violated, ", "), "\n", + " all required effects: ", str.join(required, ", "), "\n", + " grant allows: ", grant_summary_for_mode(mode) + ], ""))) + } + }, } }, } From 708e95f165d78e633fa060f1f62c07d2147b60cc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 6 Jun 2026 07:41:00 +0000 Subject: [PATCH 4/4] style: format os_check tool Co-authored-by: alpibrupa --- src/tools/os_check.lex | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/tools/os_check.lex b/src/tools/os_check.lex index 557a2f0..45595b5 100644 --- a/src/tools/os_check.lex +++ b/src/tools/os_check.lex @@ -86,12 +86,7 @@ fn execute(args :: jv.Json) -> [net, io, proc] Result[jv.Json, e.Errors] { if list.len(violated) == 0 { Ok(JStr(str.concat("grant check passed [mode=", str.concat(mode, str.concat("] effects=", str.join(required, ",")))))) } else { - Err(e.single("", "grant_violation", str.join([ - "GRANT VIOLATION [mode=", mode, "]\n", - " forbidden effects used: ", str.join(violated, ", "), "\n", - " all required effects: ", str.join(required, ", "), "\n", - " grant allows: ", grant_summary_for_mode(mode) - ], ""))) + Err(e.single("", "grant_violation", str.join(["GRANT VIOLATION [mode=", mode, "]\n", " forbidden effects used: ", str.join(violated, ", "), "\n", " all required effects: ", str.join(required, ", "), "\n", " grant allows: ", grant_summary_for_mode(mode)], ""))) } }, } @@ -120,3 +115,4 @@ fn grant_summary_for_mode(mode :: Str) -> Str { fn tool() -> t.Tool { t.define("os_check", "Check a Lex file's declared effects against the refactor-mode trust grant (lex-os integration). Run after lex_check to catch grant violations — e.g. a refactor agent must not use net effects. The grant mode is fixed by the tool and cannot be overridden by model input. Returns GRANT VIOLATION with details if the file exceeds the refactor grant.", params(), execute) } +