// W3 — production wasm workspace runtime + membrane (WASM-PROPOSAL §6, §7).
//
// One workspace per request: a fresh Wasmtime store holding the core module
// instance (production uce_lib carve-out, owns memory/allocator/DValue) plus unit
// PIC side modules loaded lazily — including mid-request via the
// uce_host_component_resolve hostcall, which is how component()/unit_render()
// inside the guest trigger dynamic loading.
//
// Designed for amalgamation-include (like uce_lib.cpp): the including TU must
// provide String/DValue/ucb_encode/ucb_decode (types.cpp + dvalue.cpp) and
// wasm_trace.h. W4 includes this from the FastCGI worker build; the W3 CLI
// driver (w3_driver.cpp) is the first consumer.
//
// Loader rules carried from the spike phases, now enforced as policy:
//  - import discipline: units may import only env / GOT.mem / GOT.func
//    (no WASI — the core is zero-WASI for UCE's own calls, and units get
//    everything from the core); units defining allocator symbols are rejected
//  - GOT.mem of a PIC module's data exports are __memory_base-relative
//    offsets; the loader adds the owning module's base (Phase 0 erratum)
//  - GOT.func resolves host-side: the target function's funcref is placed
//    into the shared table and the slot index becomes the GOT value
//  - export name collisions: core wins, then first-loading unit wins
//    (mirrors native dlopen global-symbol interposition for identical
//    vague-linkage template instantiations)
//  - uce.abi stamp must match the core ABI version; fail loudly, never guess

#include <wasmtime.hh>

#include <chrono>
#include <cmath>
#include <cstring>
#include <ctime>
#include <fstream>
#include <filesystem>
#include <map>
#include <set>
#include <cstdio>
#include <memory>
#include <optional>
#include <string>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
#include <limits.h>
#include <unistd.h>
#include <sys/time.h>
#include <vector>
#include <algorithm>
#include <dirent.h>
#include <cerrno>
#include <sys/wait.h>
#include <signal.h>
#include <poll.h>

struct WasmDylinkInfo
{
	u32 mem_size = 0;
	u32 mem_align = 0;
	u32 table_size = 0;
	u32 table_align = 0;
	bool found = false;
};

struct WasmAbiInfo
{
	u32 version = 0;
	String toolchain;
	bool found = false;
};

struct WasmUnitModule
{
	String source_path;   // absolute .uce path
	String wasm_path;     // artifact in the unit cache
	u64 modified_ns = 0;
	u64 changed_ns = 0;
	u64 size = 0;
	WasmDylinkInfo dylink;
	WasmAbiInfo abi;
	std::optional<wasmtime::Module> module;
};

struct WasmWorkerConfig
{
	String core_wasm_path = "bin/wasm/core.wasm";
	String site_root;                  // absolute
	String cache_root = "/tmp/uce/work";
	std::vector<String> write_roots;   // absolute prefixes the write membrane allows
	int64_t memory_limit = 512ll * 1024 * 1024;
	u32 table_headroom = 4096;
	u64 epoch_deadline_ticks = 200;    // ticker period × ticks = CPU budget
	bool verbose = false;
	// uce_host_* names (bare, without the "uce_host_" prefix) the sysadmin has
	// disabled via UCE_HOSTCALL_BLOCKLIST. A blocked hostcall resolves to a trap
	// stub at workspace birth (see make_host_import); empty = feature off.
	std::set<String> hostcall_blocklist;
};

struct WasmResponse
{
	bool ok = false;
	bool handler_present = true;  // false → unit has no handler for the requested kind (404)
	String body;
	DValue meta;          // status / headers / cookies / session
	String error;         // collapsed trace or loader error when !ok
	u64 workspace_birth_us = 0;
	u64 component_resolve_count = 0;
	u64 component_resolve_total_us = 0;
};

static u64 wasm_file_lock_timeout_ms()
{
	const char* raw = getenv("UCE_FILE_LOCK_TIMEOUT_MS");
	if(!raw || !*raw)
		return(2000);
	char* end = 0;
	unsigned long long parsed = strtoull(raw, &end, 10);
	return(end == raw ? 2000 : (u64)parsed);
}

static u64 wasm_monotonic_ms()
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return((u64)ts.tv_sec * 1000ull + (u64)ts.tv_nsec / 1000000ull);
}

static int wasm_open_locked_file(const String& file_name, int flags, int lock_type, bool truncate_after_lock)
{
	int fd = open(file_name.c_str(), flags, 0644);
	if(fd < 0)
		return(-1);
	fcntl(fd, F_SETFD, FD_CLOEXEC);
	u64 timeout = wasm_file_lock_timeout_ms();
	u64 deadline = wasm_monotonic_ms() + timeout;
	while(true)
	{
		if(flock(fd, lock_type | LOCK_NB) == 0)
		{
			if(truncate_after_lock && ftruncate(fd, 0) != 0)
			{
				flock(fd, LOCK_UN);
				close(fd);
				return(-1);
			}
			return(fd);
		}
		if(errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR)
		{
			close(fd);
			return(-1);
		}
		if(timeout == 0 || wasm_monotonic_ms() >= deadline)
		{
			fprintf(stderr, "[wasm] file lock timeout after %llums: %s\n", (unsigned long long)timeout, file_name.c_str());
			close(fd);
			return(-1);
		}
		usleep(10000);
	}
}

static bool wasm_fd_write_all(int fd, const char* data, size_t remaining, u64* written_out)
{
	u64 total = 0;
	while(remaining > 0)
	{
		ssize_t n = write(fd, data, remaining);
		if(n < 0)
		{
			if(errno == EINTR)
				continue;
			return(false);
		}
		if(n == 0)
			return(false);
		data += n;
		remaining -= (size_t)n;
		total += (u64)n;
	}
	if(written_out)
		*written_out = total;
	return(true);
}



// ---- file-backed async job registry + bounded process execution ----------

static String uce_job_root()
{
	const char* env = getenv("UCE_JOB_ROOT");
	String root = (env && *env) ? String(env) : String("/run/uce/jobs");
	std::error_code ec;
	std::filesystem::create_directories(root, ec);
	if(ec)
	{
		root = "/tmp/uce/jobs";
		std::filesystem::create_directories(root, ec);
	}
	return(root);
}

static String uce_job_path(u64 id) { return(uce_job_root() + "/" + std::to_string(id)); }
static String uce_read_text(const String& path) { std::ifstream in(path, std::ios::binary); if(!in) return(""); std::ostringstream ss; ss << in.rdbuf(); return(ss.str()); }
static void uce_write_text(const String& path, const String& data) { std::ofstream out(path, std::ios::binary|std::ios::trunc); out.write(data.data(), (std::streamsize)data.size()); }

static u64 uce_job_new(const String& kind)
{
	String root = uce_job_root();
	std::error_code ec;
	std::filesystem::create_directories(root, ec);
	u64 seed = ((u64)time(0) << 32) ^ ((u64)getpid() << 16) ^ (u64)rand();
	for(int i = 0; i < 100; i++)
	{
		u64 id = seed ^ (wasm_monotonic_ms() + (u64)i * 0x9e3779b97f4a7c15ull);
		String dir = root + "/" + std::to_string(id);
		if(mkdir(dir.c_str(), 0700) == 0)
		{
			uce_write_text(dir + "/kind", kind);
			uce_write_text(dir + "/created", std::to_string((u64)time(0)));
			uce_write_text(dir + "/state", "pending");
			return(id);
		}
	}
	return(0);
}

static void uce_job_reap()
{
	String root = uce_job_root();
	u64 now = (u64)time(0);
	u64 ttl = 3600;
	if(const char* raw = getenv("UCE_JOB_TTL_SECONDS")) { char* e=0; unsigned long long v=strtoull(raw,&e,10); if(e!=raw && v>0) ttl=(u64)v; }
	std::error_code ec;
	for(auto& e : std::filesystem::directory_iterator(root, ec))
	{
		if(!e.is_directory()) continue;
		u64 created = strtoull(uce_read_text(e.path().string()+"/created").c_str(), 0, 10);
		if(created > 0 && now > created + ttl)
			std::filesystem::remove_all(e.path(), ec);
	}
}

static DValue uce_process_exec(String cmd, String input, StringMap env, u64 timeout_ms)
{
	DValue r;
	r["exit_code"] = (f64)-1;
	r["stdout"] = "";
	r["stderr"] = "";
	r["timed_out"].set_bool(false);
	if(timeout_ms == 0) timeout_ms = 5000;
	int inpipe[2], outpipe[2], errpipe[2];
	if(pipe(inpipe) || pipe(outpipe) || pipe(errpipe)) { r["stderr"]="pipe failed"; return(r); }
	pid_t pid = fork();
	if(pid == 0)
	{
		dup2(inpipe[0], 0); dup2(outpipe[1], 1); dup2(errpipe[1], 2);
		close(inpipe[0]); close(inpipe[1]); close(outpipe[0]); close(outpipe[1]); close(errpipe[0]); close(errpipe[1]);
		for(auto& kv : env) setenv(kv.first.c_str(), kv.second.c_str(), 1);
		execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
		_exit(127);
	}
	close(inpipe[0]); close(outpipe[1]); close(errpipe[1]);
	fcntl(outpipe[0], F_SETFL, fcntl(outpipe[0], F_GETFL, 0) | O_NONBLOCK);
	fcntl(errpipe[0], F_SETFL, fcntl(errpipe[0], F_GETFL, 0) | O_NONBLOCK);
	size_t input_off = 0; bool in_open = true, out_open = true, err_open = true; int status = 0; bool exited = false;
	u64 deadline = wasm_monotonic_ms() + timeout_ms;
	while(out_open || err_open || !exited)
	{
		if(!exited)
		{
			pid_t w = waitpid(pid, &status, WNOHANG);
			if(w == pid) exited = true;
		}
		if(in_open)
		{
			if(input_off < input.size()) { ssize_t n=write(inpipe[1], input.data()+input_off, input.size()-input_off); if(n>0) input_off += (size_t)n; else if(n<0 && errno!=EINTR) { close(inpipe[1]); in_open=false; } }
			else { close(inpipe[1]); in_open=false; }
		}
		char buf[4096];
		ssize_t n;
		while((n=read(outpipe[0], buf, sizeof(buf))) > 0) r["stdout"] = r["stdout"].to_string() + String(buf, n);
		if(n == 0 && out_open) { close(outpipe[0]); out_open=false; }
		while((n=read(errpipe[0], buf, sizeof(buf))) > 0) r["stderr"] = r["stderr"].to_string() + String(buf, n);
		if(n == 0 && err_open) { close(errpipe[0]); err_open=false; }
		if(!exited && wasm_monotonic_ms() >= deadline)
		{
			r["timed_out"].set_bool(true);
			kill(pid, SIGKILL);
			waitpid(pid, &status, 0);
			exited = true;
		}
		if((out_open || err_open || !exited)) usleep(10000);
	}
	if(WIFEXITED(status)) { r["exit_code"] = (f64)WEXITSTATUS(status); r["timed_out"].set_bool(false); }
	else if(WIFSIGNALED(status)) r["exit_code"] = (f64)(128 + WTERMSIG(status));
	return(r);
}

static DValue uce_shell_exec_spec(const DValue& spec)
{
	return(uce_process_exec(spec.key("cmd") ? spec.key("cmd")->to_string() : String(""), spec.key("stdin") ? spec.key("stdin")->to_string() : String(""), spec.key("env") ? spec.key("env")->to_stringmap() : StringMap(), spec.key("timeout_ms") ? spec.key("timeout_ms")->to_u64(5000) : 5000));
}

static void uce_job_finish(u64 id, DValue result, String final_state="done")
{
	String dir = uce_job_path(id);
	uce_write_text(dir + "/result.tmp", ucb_encode(result));
	rename((dir + "/result.tmp").c_str(), (dir + "/result").c_str());
	uce_write_text(dir + "/state", final_state);
}

static u64 uce_shell_spawn_spec(const DValue& spec)
{
	uce_job_reap();
	u64 id = uce_job_new("shell");
	if(!id) return(0);
	pid_t pid = fork();
	if(pid == 0)
	{
		setsid();
		uce_write_text(uce_job_path(id) + "/worker_pid", std::to_string((long long)getpid()));
		uce_write_text(uce_job_path(id) + "/state", "running");
		DValue result = uce_shell_exec_spec(spec);
		uce_job_finish(id, result, "done");
		_exit(0);
	}
	if(pid < 0) { DValue r; r["error"]="fork failed"; uce_job_finish(id,r,"failed"); return(id); }
	uce_write_text(uce_job_path(id) + "/worker_pid", std::to_string((long long)pid));
	uce_write_text(uce_job_path(id) + "/state", "running");
	return(id);
}


static DValue uce_exec_argv_capture(std::vector<String> argv, String input, u64 timeout_ms)
{
	DValue r; r["exit_code"]=(f64)-1; r["stdout"]=""; r["stderr"]=""; r["timed_out"].set_bool(false);
	if(argv.empty()) { r["stderr"]="empty argv"; return(r); }
	if(timeout_ms == 0) timeout_ms = 5000;
	int inpipe[2], outpipe[2], errpipe[2];
	if(pipe(inpipe)||pipe(outpipe)||pipe(errpipe)) { r["stderr"]="pipe failed"; return(r); }
	pid_t pid=fork();
	if(pid==0)
	{
		dup2(inpipe[0],0); dup2(outpipe[1],1); dup2(errpipe[1],2);
		close(inpipe[0]); close(inpipe[1]); close(outpipe[0]); close(outpipe[1]); close(errpipe[0]); close(errpipe[1]);
		std::vector<char*> args; for(auto& a: argv) args.push_back((char*)a.c_str()); args.push_back(0);
		execvp(args[0], args.data()); _exit(127);
	}
	close(inpipe[0]); close(outpipe[1]); close(errpipe[1]);
	fcntl(outpipe[0], F_SETFL, fcntl(outpipe[0], F_GETFL, 0)|O_NONBLOCK); fcntl(errpipe[0], F_SETFL, fcntl(errpipe[0], F_GETFL, 0)|O_NONBLOCK);
	size_t input_off=0; bool in_open=true,out_open=true,err_open=true,exited=false; int status=0; u64 deadline=wasm_monotonic_ms()+timeout_ms;
	while(out_open || err_open || !exited)
	{
		if(!exited) { pid_t w=waitpid(pid,&status,WNOHANG); if(w==pid) exited=true; }
		if(in_open) { if(input_off<input.size()) { ssize_t n=write(inpipe[1], input.data()+input_off, input.size()-input_off); if(n>0) input_off+=(size_t)n; else if(n<0 && errno!=EINTR) { close(inpipe[1]); in_open=false; } } else { close(inpipe[1]); in_open=false; } }
		char buf[4096]; ssize_t n; while((n=read(outpipe[0],buf,sizeof(buf)))>0) r["stdout"] = r["stdout"].to_string()+String(buf,n); if(n==0&&out_open){close(outpipe[0]);out_open=false;}
		while((n=read(errpipe[0],buf,sizeof(buf)))>0) r["stderr"] = r["stderr"].to_string()+String(buf,n); if(n==0&&err_open){close(errpipe[0]);err_open=false;}
		if(!exited && wasm_monotonic_ms() >= deadline) { r["timed_out"].set_bool(true); kill(pid,SIGKILL); waitpid(pid,&status,0); exited=true; }
		if(out_open || err_open || !exited) usleep(10000);
	}
	if(WIFEXITED(status)) { r["exit_code"]=(f64)WEXITSTATUS(status); r["timed_out"].set_bool(false); } else if(WIFSIGNALED(status)) r["exit_code"]=(f64)(128+WTERMSIG(status));
	return(r);
}

static bool uce_header_name_safe(String name)
{
	if(name=="") return(false);
	for(unsigned char c: name) if(!(isalnum(c)||c=='-'||c=='_')) return(false);
	return(true);
}

static DValue uce_http_request_value(const DValue& req)
{
	DValue r; r["status"]=(f64)0; r["headers"].set_array(); r["body"]=""; r["error"]="";
	const DValue* method_value = req.key("method");
	const DValue* url_value = req.key("url");
	const DValue* timeout_value = req.key("timeout_ms");
	const DValue* follow_redirects_value = req.key("follow_redirects");
	const DValue* headers_value = req.key("headers");
	const DValue* body_value = req.key("body");
	String method = method_value ? to_upper(method_value->to_string()) : String("GET");
	String url = url_value ? url_value->to_string() : String("");
	if(url=="" || url.find('\0')!=String::npos) { r["error"]="missing url"; return(r); }
	if(method=="") method="GET";
	u64 timeout_ms = timeout_value ? timeout_value->to_u64(5000) : 5000;
	std::vector<String> argv = {"curl", "-sS", "--http1.0", "-X", method, "-D", "-", "-w", "\nUCE_HTTP_STATUS:%{http_code}", "--max-time", std::to_string(std::max<u64>(1, timeout_ms / 1000))};
	if(follow_redirects_value && follow_redirects_value->to_bool()) argv.push_back("-L");
	if(headers_value) headers_value->each([&](const DValue& v, String k){ if(uce_header_name_safe(k)) { argv.push_back("-H"); argv.push_back(k + ": " + replace(replace(v.to_string(), "\r", " "), "\n", " ")); } });
	String body = body_value ? body_value->to_string() : String("");
	if(body_value) { argv.push_back("--data-binary"); argv.push_back("@-"); }
	argv.push_back(url);
	if(access("/usr/bin/curl", X_OK)!=0 && access("/bin/curl", X_OK)!=0) { r["error"]="curl binary not found in runtime PATH"; return(r); }
	DValue pr = uce_exec_argv_capture(argv, body, timeout_ms);
	String out = pr["stdout"].to_string();
	String marker="\nUCE_HTTP_STATUS:"; size_t mp=out.rfind(marker);
	if(mp!=String::npos) { r["status"]=(f64)strtoull(out.c_str()+mp+marker.size(),0,10); out=out.substr(0,mp); }
	else r["error"]="curl did not report status";
	String sep="\r\n\r\n"; size_t hp=out.rfind(sep); size_t sep_len=4; if(hp==String::npos) { sep="\n\n"; hp=out.rfind(sep); sep_len=2; }
	String hdrs = hp==String::npos ? String("") : out.substr(0,hp); r["body"] = hp==String::npos ? out : out.substr(hp+sep_len);
	DValue headers;
	for(String line: split(replace(hdrs,"\r",""), "\n")) { size_t c=line.find(':'); if(c!=String::npos) headers[trim(line.substr(0,c))] = trim(line.substr(c+1)); }
	r["headers"] = headers;
	if(pr["exit_code"].to_s64() != 0 && r["error"].to_string()=="") r["error"] = trim(pr["stderr"].to_string());
	return(r);
}

static u64 uce_http_spawn_spec(const DValue& req)
{
	uce_job_reap(); u64 id=uce_job_new("http"); if(!id) return(0);
	pid_t pid=fork();
	if(pid==0) { setsid(); uce_write_text(uce_job_path(id)+"/worker_pid", std::to_string((long long)getpid())); uce_write_text(uce_job_path(id)+"/state", "running"); DValue result=uce_http_request_value(req); uce_job_finish(id,result,result["error"].to_string()==""?"done":"failed"); _exit(0); }
	if(pid<0) { DValue r; r["error"]="fork failed"; uce_job_finish(id,r,"failed"); return(id); }
	uce_write_text(uce_job_path(id)+"/worker_pid", std::to_string((long long)pid)); uce_write_text(uce_job_path(id)+"/state", "running"); return(id);
}

static DValue uce_job_status_value(u64 id)
{
	DValue r; String dir=uce_job_path(id); r["job_id"]=(f64)id;
	if(id==0 || !std::filesystem::is_directory(dir)) { r["state"]="missing"; return(r); }
	String state=trim(uce_read_text(dir+"/state")); if(state=="") state="pending"; r["state"]=state;
	r["kind"]=trim(uce_read_text(dir+"/kind")); r["pid"]=(f64)strtoull(uce_read_text(dir+"/worker_pid").c_str(),0,10);
	r["done"].set_bool(state=="done"||state=="failed"||state=="cancelled");
	return(r);
}

static DValue uce_job_result_value(u64 id, u64 timeout_ms)
{
	u64 deadline = wasm_monotonic_ms() + timeout_ms;
	while(timeout_ms > 0 && wasm_monotonic_ms() < deadline)
	{
		DValue st = uce_job_status_value(id);
		if(st["done"].to_bool()) break;
		usleep(10000);
	}
	DValue r = uce_job_status_value(id);
	String encoded = uce_read_text(uce_job_path(id)+"/result");
	if(encoded != "") { DValue decoded; String err; if(ucb_decode(encoded, decoded, &err)) r["result"] = decoded; }
	return(r);
}

static bool uce_job_cancel_value(u64 id)
{
	DValue st = uce_job_status_value(id);
	if(st["state"].to_string()=="missing") return(false);
	String state = st["state"].to_string();
	if(state == "done" || state == "failed" || state == "cancelled")
		return(false);
	pid_t pid = (pid_t)st["pid"].to_u64(0);
	if(pid > 0) kill(-pid, SIGKILL);
	DValue result; result["cancelled"].set_bool(true);
	uce_job_finish(id, result, "cancelled");
	return(true);
}

// ---- module byte parsing (hardened; carried from the phase 3 spike) -------

// included into both w3_driver.cpp and the native server TU (via backend.cpp);
// file-scope helpers are static so each TU gets its own copy with no clash
static bool wasm_read_uleb(const std::vector<u8>& buf, size_t& pos, size_t end, u64& out)
{
	out = 0;
	u32 shift = 0;
	while(true)
	{
		if(pos >= end || shift >= 64)
			return(false);
		u8 byte = buf[pos++];
		out |= ((u64)(byte & 0x7f)) << shift;
		if((byte & 0x80) == 0)
			return(true);
		shift += 7;
	}
}

static bool wasm_parse_sections(const std::vector<u8>& bytes, WasmDylinkInfo& dylink, WasmAbiInfo& abi, String& error)
{
	if(bytes.size() < 8 || memcmp(bytes.data(), "\0asm", 4) != 0)
	{
		error = "not a wasm module";
		return(false);
	}
	if(!(bytes[4] == 1 && bytes[5] == 0 && bytes[6] == 0 && bytes[7] == 0))
	{
		error = "unsupported wasm binary version";
		return(false);
	}
	size_t pos = 8;
	while(pos < bytes.size())
	{
		u8 section_id = bytes[pos++];
		u64 size = 0;
		if(!wasm_read_uleb(bytes, pos, bytes.size(), size) || size > bytes.size() - pos)
		{
			error = "malformed wasm section header";
			return(false);
		}
		size_t end = pos + (size_t)size;
		if(section_id == 0)
		{
			u64 name_len = 0;
			size_t cursor = pos;
			if(!wasm_read_uleb(bytes, cursor, end, name_len) || name_len > end - cursor)
			{
				error = "malformed custom section name";
				return(false);
			}
			String name((const char*)bytes.data() + cursor, (size_t)name_len);
			cursor += (size_t)name_len;
			if(name == "dylink.0")
			{
				while(cursor < end)
				{
					u8 sub = bytes[cursor++];
					u64 sub_len = 0;
					if(!wasm_read_uleb(bytes, cursor, end, sub_len) || sub_len > end - cursor)
					{
						error = "malformed dylink.0 subsection";
						return(false);
					}
					size_t sub_end = cursor + (size_t)sub_len;
					if(sub == 1) // WASM_DYLINK_MEM_INFO
					{
						u64 v[4];
						for(int i = 0; i < 4; i++)
							if(!wasm_read_uleb(bytes, cursor, sub_end, v[i]))
							{
								error = "malformed dylink.0 mem_info";
								return(false);
							}
						if(dylink.found)
						{
							error = "duplicate dylink.0 mem_info";
							return(false);
						}
						if(v[1] >= 31 || v[3] >= 31)
						{
							error = "unsupported dylink alignment";
							return(false);
						}
						dylink.mem_size = (u32)v[0];
						dylink.mem_align = (u32)v[1];
						dylink.table_size = (u32)v[2];
						dylink.table_align = (u32)v[3];
						dylink.found = true;
					}
					cursor = sub_end;
				}
			}
			else if(name == "uce.abi")
			{
				String text((const char*)bytes.data() + cursor, end - cursor);
				abi.found = true;
				size_t line_start = 0;
				while(line_start < text.size())
				{
					size_t line_end = text.find('\n', line_start);
					if(line_end == String::npos)
						line_end = text.size();
					String line = text.substr(line_start, line_end - line_start);
					if(line.rfind("unit_abi_version=", 0) == 0)
						abi.version = (u32)strtoul(line.c_str() + 17, 0, 10);
					if(line.rfind("toolchain=", 0) == 0)
						abi.toolchain = line.substr(10);
					line_start = line_end + 1;
				}
			}
		}
		pos = end;
	}
	return(true);
}

static bool wasm_read_file(const String& path, std::vector<u8>& out)
{
	std::ifstream in(path, std::ios::binary);
	if(!in)
		return(false);
	in.seekg(0, std::ios::end);
	std::streamoff n = in.tellg();
	if(n < 0)
		return(false);
	in.seekg(0, std::ios::beg);
	out.resize((size_t)n);
	if(n == 0)
		return(true);
	in.read((char*)out.data(), n);
	return((bool)in);
}

// ---- worker (per process): engine + compiled module caches ----------------

class WasmWorker
{
public:
	WasmWorkerConfig cfg;

	explicit WasmWorker(WasmWorkerConfig config) : cfg(std::move(config)), engine(make_engine())
	{
	}

	String init()
	{
		std::vector<u8> bytes;
		if(!wasm_read_file(cfg.core_wasm_path, bytes))
			return("cannot read core module: " + cfg.core_wasm_path);
		WasmAbiInfo abi_ignored;
		String parse_error;
		if(!wasm_parse_sections(bytes, core_dylink, abi_ignored, parse_error))
			return("core module: " + parse_error);

		String core_cached_path = cached_wasm_path(cfg.core_wasm_path);
		String compile_error;
		auto compiled_or_cached = load_or_compile_cached_module(engine, core_cached_path, cfg.core_wasm_path, bytes, compile_error);
		if(!compiled_or_cached)
			return("core module compile failed: " + compile_error);
		core_module.emplace(std::move(*compiled_or_cached));
		return("");
	}

	wasmtime::Engine engine;
	std::optional<wasmtime::Module> core_module;
	WasmDylinkInfo core_dylink;

	// unit artifact path in the W2 cache: cache_root + <abs source dir> + /<file>.wasm
	String unit_wasm_path(const String& source_path) const
	{
		return(cfg.cache_root + source_path + ".wasm");
	}

	std::shared_ptr<WasmUnitModule> unit_module(const String& source_path, String& error)
	{
		String wasm_path = unit_wasm_path(source_path);
		struct stat st;
		if(stat(wasm_path.c_str(), &st) != 0)
		{
			error = "no wasm artifact for " + source_path + " (expected " + wasm_path + ")";
			return(nullptr);
		}
		auto cached = module_cache.find(wasm_path);
		u64 modified_ns = (u64)st.st_mtim.tv_sec * 1000000000ull + (u64)st.st_mtim.tv_nsec;
		u64 changed_ns = (u64)st.st_ctim.tv_sec * 1000000000ull + (u64)st.st_ctim.tv_nsec;
		if(cached != module_cache.end() && cached->second->modified_ns == modified_ns && cached->second->changed_ns == changed_ns && cached->second->size == (u64)st.st_size)
			return(cached->second);

		auto unit = std::make_shared<WasmUnitModule>();
		unit->source_path = source_path;
		unit->wasm_path = wasm_path;
		unit->modified_ns = modified_ns;
		unit->changed_ns = changed_ns;
		unit->size = (u64)st.st_size;
		std::vector<u8> bytes;
		if(!wasm_read_file(wasm_path, bytes))
		{
			error = "cannot read " + wasm_path;
			return(nullptr);
		}
		if(!wasm_parse_sections(bytes, unit->dylink, unit->abi, error))
		{
			error = wasm_path + ": " + error;
			return(nullptr);
		}
		if(!unit->dylink.found)
		{
			error = wasm_path + ": missing dylink.0 mem_info (not a PIC side module)";
			return(nullptr);
		}
		if(!unit->abi.found)
		{
			error = wasm_path + ": missing uce.abi stamp";
			return(nullptr);
		}
		String unit_cached_path = cached_wasm_path(wasm_path);
		String compile_error;
		auto compiled_or_cached = load_or_compile_cached_module(engine, unit_cached_path, wasm_path, bytes, compile_error);
		if(!compiled_or_cached)
		{
			error = wasm_path + ": compile failed: " + compile_error;
			return(nullptr);
		}
		unit->module.emplace(std::move(*compiled_or_cached));
		module_cache[wasm_path] = unit;
		return(unit);
	}

private:
	static String cached_wasm_path(const String& wasm_path)
	{
		if(wasm_path.size() >= 5 && wasm_path.rfind(".wasm", wasm_path.size() - 5) == wasm_path.size() - 5)
			return(wasm_path.substr(0, wasm_path.size() - 5) + ".cwasm");
		return(wasm_path + ".cwasm");
	}

	static std::optional<wasmtime::Module> load_or_compile_cached_module(wasmtime::Engine& engine, const String& cached_path, const String& wasm_path,
		std::vector<u8>& bytes, String& compile_error)
	{
		struct stat wasm_stat;
		struct stat cached_stat;
		if(stat(cached_path.c_str(), &cached_stat) == 0 && stat(wasm_path.c_str(), &wasm_stat) == 0)
		{
			if(cached_stat.st_mtim.tv_sec > wasm_stat.st_mtim.tv_sec ||
				(cached_stat.st_mtim.tv_sec == wasm_stat.st_mtim.tv_sec && cached_stat.st_mtim.tv_nsec > wasm_stat.st_mtim.tv_nsec))
			{
				auto deserialized = wasmtime::Module::deserialize_file(engine, cached_path);
				if(deserialized)
					return(deserialized.ok());
			}
		}

		auto compiled = wasmtime::Module::compile(engine, bytes);
		if(!compiled)
		{
			compile_error = String(compiled.err().message());
			return(std::nullopt);
		}
		auto result = compiled.ok();
		auto serialized = result.serialize();
		if(serialized)
		{
			String tmp = cached_path + "." + std::to_string((long long)getpid()) + ".tmp";
			auto data = serialized.ok();
			{
				std::ofstream out(tmp, std::ios::binary | std::ios::trunc);
				if(out)
				{
					out.write((const char*)data.data(), (std::streamsize)data.size());
					if(out)
					{
						out.flush();
						out.close();
						if(std::rename(tmp.c_str(), cached_path.c_str()) != 0)
							(void)std::remove(tmp.c_str());
					}
				}
				else
					(void)std::remove(tmp.c_str());
			}
		}
		return(result);
	}

	static wasmtime::Engine make_engine()
	{
		wasmtime::Config config;
		config.epoch_interruption(true);
		// CRITICAL: the host (linux_fastcgi.cpp) installs its own SIGSEGV/SIGILL/
		// SIGBUS handlers per request (install_request_fault_handlers) with plain
		// signal(), which clobbers Wasmtime's trap-handling handlers. With
		// signals_based_traps on, a guest `unreachable`/OOB surfaces as a host
		// signal that the native on_segfault catches and abort()s the worker —
		// a 502 instead of a clean wasm trap. Disabling it makes Cranelift emit
		// explicit trap checks: guest traps stay pure wasm traps returned as
		// Result errors, never a host signal, so the two never collide.
		config.signals_based_traps(false);
		return(wasmtime::Engine(std::move(config)));
	}

	std::map<String, std::shared_ptr<WasmUnitModule>> module_cache;
};

// ---- workspace (per request) ----------------------------------------------

class WasmWorkspace
{
public:
	WasmWorker& worker;
	wasmtime::Store store;
	u64 workspace_birth_us = 0;
	u64 component_resolve_count = 0;
	u64 component_resolve_total_us = 0;

	struct FileHandle
	{
		int fd = -1;
		bool writable = false;
	};
	std::vector<FileHandle> file_handles;

	struct RequestPerfSnapshot
	{
		u64 worker_pid = 0;
		u64 parent_pid = 0;
		u64 request_count = 0;
		f64 time_init = 0;
		f64 time_start = 0;
		bool active = false;
	} request_perf;

	explicit WasmWorkspace(WasmWorker& w) : worker(w), store(w.engine)
	{
	}

	void set_perf_snapshot(u64 worker_pid, u64 parent_pid, u64 request_count, f64 time_init, f64 time_start)
	{
		request_perf.worker_pid = worker_pid;
		request_perf.parent_pid = parent_pid;
		request_perf.request_count = request_count;
		request_perf.time_init = time_init;
		request_perf.time_start = time_start;
		request_perf.active = true;
	}

#ifdef UCE_WASM_HOST_CONNECTORS
	// Host-owned resource handle table (§3.1): connections opened by the guest
	// live here and are closed when the workspace drops at request end. This is
	// the wasm-side enforcement of request-scoped DB lifecycle; app code should
	// never cache these opaque handles across requests.
	std::vector<SQLite*> sqlite_handles;
	std::vector<MySQL*> mysql_handles;
	std::vector<MySQL*> mysql_request_pool;
#endif
	~WasmWorkspace()
	{
		for(auto& h : file_handles)
		{
			if(h.fd >= 0)
			{
				flock(h.fd, LOCK_UN);
				close(h.fd);
				h.fd = -1;
			}
		}
#ifdef UCE_WASM_HOST_CONNECTORS
		for(auto* db : sqlite_handles)
			if(db)
				delete db;   // ~SQLite disconnects
		for(auto* db : mysql_request_pool)
			if(db)
				delete db;   // ~MySQL disconnects
#endif
	}

	// The guest calls a sized hostcall twice (buf=0 to learn the length, then
	// to fetch). For side-effecting ops (sqlite) re-executing on the fetch is
	// wrong, so the result is staged on the first call (keyed on the exact
	// input bytes) and replayed on the second without re-running the op.
	String staged_hostcall_input;
	String staged_hostcall_result;
	String staged_socket_read_key;
	String staged_socket_read_result;
	String staged_memcache_key;
	String staged_memcache_result;
	bool hostcall_staged(const String& input, String& out)
	{
		if(!staged_hostcall_input.empty() && input == staged_hostcall_input)
		{
			out = staged_hostcall_result;
			staged_hostcall_input = "";
			staged_hostcall_result = "";
			return(true);
		}
		return(false);
	}
	void hostcall_stage(const String& input, const String& out)
	{
		staged_hostcall_input = input;
		staged_hostcall_result = out;
	}

	// resolve-kind values shared with the guest core (src/wasm/core.cpp)
	// must match WasmResolveKind in src/wasm/core.cpp

	String birth()
	{
		auto cx = ctx();
		store.limiter(worker.cfg.memory_limit, -1, -1, -1, -1);
		cx.set_epoch_deadline(worker.cfg.epoch_deadline_ticks);

		auto& module = *worker.core_module;
		std::vector<wasmtime::Extern> imports;
		for(auto import_type : module.imports())
		{
			String mod(import_type.module());
			String name(import_type.name());
			auto extern_type = wasmtime::ExternType::from_import(import_type);
			if(auto* table_ref = std::get_if<wasmtime::TableType::Ref>(&extern_type))
			{
				if(name.rfind("__indirect_function_table", 0) != 0)
					return("core imports unexpected table " + mod + "." + name);
				u32 core_min = table_ref->min();
				u32 total = core_min + worker.cfg.table_headroom;
				wasmtime::TableType table_type(wasmtime::ValType::funcref(), total, total);
				auto created = wasmtime::Table::create(cx, table_type, wasmtime::Val(std::optional<wasmtime::Func>()));
				if(!created)
					return("table create failed: " + String(created.err().message()));
				table.emplace(created.ok());
				table_next_free = core_min;
				imports.push_back(*table);
				continue;
			}
			auto* func_ref = std::get_if<wasmtime::FuncType::Ref>(&extern_type);
			if(!func_ref)
				return("core has unexpected non-func import " + mod + "." + name);
			wasmtime::FuncType func_type(*func_ref);
			imports.push_back(make_host_import(cx, mod, name, func_type));
		}
		if(!table)
			return("core does not import __indirect_function_table — rebuild core with --import-table");

		auto created = wasmtime::Instance::create(cx, module, imports);
		if(!created)
			return("core instantiation failed: " + trap_text(created.err()));
		core.emplace(created.ok());

		auto exported_memory = core->get(cx, "memory");
		if(!exported_memory || !std::get_if<wasmtime::Memory>(&*exported_memory))
			return("core does not export memory");
		memory.emplace(std::get<wasmtime::Memory>(*exported_memory));

		String error = call_core("_initialize", {}, 0);
		if(error != "")
			return(error);
		int32_t rc = 0;
		error = call_core("uce_wasm_core_init", {}, &rc);
		if(error != "")
			return(error);
		if(rc != 0)
			return("uce_wasm_core_init returned " + std::to_string(rc));
		int32_t core_abi = 0;
		error = call_core("uce_wasm_core_abi_version", {}, &core_abi);
		if(error != "")
			return(error);
		abi_version = (u32)core_abi;
		error = call_core("uce_wasm_request", {}, &request_ptr);
		if(error != "")
			return(error);
		if(request_ptr == 0)
			return("core returned null Request*");
		return("");
	}

	String apply_context(const DValue& context_tree)
	{
		String encoded = ucb_encode(context_tree);
		int32_t guest_ptr = 0;
		String error = call_core("uce_alloc", { (int32_t)encoded.size() }, &guest_ptr);
		if(error != "")
			return(error);
		if(guest_ptr == 0)
			return("guest uce_alloc failed for context buffer");
		error = guest_write((u32)guest_ptr, encoded);
		if(error != "")
			return(error);
		int32_t rc = 0;
		error = call_core("uce_wasm_apply_context", { guest_ptr, (int32_t)encoded.size() }, &rc);
		if(error != "")
			return(error);
		call_core("uce_free", { guest_ptr }, 0);
		if(rc != 0)
			return("uce_wasm_apply_context returned " + std::to_string(rc));
		return("");
	}

	// Invoke the entry unit through a named handler ("render"/"cli"/"websocket"/
	// "serve_http:named"). The handler is just an export name — same resolve +
	// ONCE + dispatch path as a component. handler_present, when provided,
	// reports whether the unit exports it (caller maps a missing render to an
	// empty body, a missing cli/serve handler to a 404).
	String invoke_entry(const String& entry_source_path, const String& handler, bool* handler_present = 0)
	{
		entry_dir = dir_of(entry_source_path);
		size_t unit_index = 0;
		String error = load_unit(entry_source_path, unit_index);
		if(error != "")
			return(error);
		bool present = (bool)unit_func(unit_index, handler_export_symbol(handler));
		if(handler_present)
			*handler_present = present;
		if(!present)
			return("");
		// Invoke through the core: it resolves the same handler and runs ONCE +
		// dispatch (unit is pre-loaded above; resolution is cached). The core
		// entry takes two guest buffers — the unit path and the handler name.
		auto entry = core_func("uce_wasm_invoke_entry");
		if(!entry)
			return("core does not export uce_wasm_invoke_entry");
		int32_t path_ptr = 0, handler_ptr = 0;
		error = call_core("uce_alloc", { (int32_t)entry_source_path.size() }, &path_ptr);
		if(error != "" || path_ptr == 0)
			return(error == "" ? String("guest uce_alloc failed for entry path") : error);
		error = guest_write((u32)path_ptr, entry_source_path);
		if(error == "")
		{
			error = call_core("uce_alloc", { (int32_t)handler.size() }, &handler_ptr);
			if(error == "" && handler_ptr == 0)
				error = "guest uce_alloc failed for handler";
		}
		if(error == "")
			error = guest_write((u32)handler_ptr, handler);
		if(error != "")
		{
			if(path_ptr) call_core("uce_free", { path_ptr }, 0);
			if(handler_ptr) call_core("uce_free", { handler_ptr }, 0);
			return(error);
		}
		auto result = entry->call(ctx(), { wasmtime::Val(path_ptr), wasmtime::Val((int32_t)entry_source_path.size()),
			wasmtime::Val(handler_ptr), wasmtime::Val((int32_t)handler.size()) });
		call_core("uce_free", { path_ptr }, 0);
		call_core("uce_free", { handler_ptr }, 0);
		if(!result)
			return(trap_text(result.err()));
		return("");
	}

	String collect(WasmResponse& response)
	{
		String error = call_core("uce_wasm_finish_output", {}, 0);
		if(error != "")
			return(error);
		error = call_core("uce_wasm_finish_response_meta", {}, 0);
		if(error != "")
			return(error);
		int32_t body_ptr = 0, body_len = 0, meta_ptr = 0, meta_len = 0;
		if((error = call_core("uce_wasm_output_data", {}, &body_ptr)) != "") return(error);
		if((error = call_core("uce_wasm_output_size", {}, &body_len)) != "") return(error);
		if((error = call_core("uce_wasm_response_meta_data", {}, &meta_ptr)) != "") return(error);
		if((error = call_core("uce_wasm_response_meta_size", {}, &meta_len)) != "") return(error);
		error = guest_read((u32)body_ptr, (u32)body_len, response.body);
		if(error != "")
			return(error);
		String meta_encoded;
		error = guest_read((u32)meta_ptr, (u32)meta_len, meta_encoded);
		if(error != "")
			return(error);
		String decode_error;
		if(!ucb_decode(meta_encoded, response.meta, &decode_error))
			return("response meta decode failed: " + decode_error);
		return("");
	}

private:
	std::optional<wasmtime::Instance> core;
	std::optional<wasmtime::Memory> memory;
	std::optional<wasmtime::Table> table;
	u32 table_next_free = 0;
	u32 abi_version = 0;
	int32_t request_ptr = 0;
	String entry_dir;

	struct LoadedUnit
	{
		std::shared_ptr<WasmUnitModule> mod;
		std::optional<wasmtime::Instance> instance;
		u32 memory_base = 0;
	};
	std::vector<LoadedUnit> units;
	std::map<String, size_t> units_by_source;
	std::map<String, u32> handler_slots;     // source + ":" + symbol → table slot
	std::vector<wasmtime::Func> host_funcs;  // keep host imports alive

	wasmtime::Store::Context ctx()
	{
		return(wasmtime::Store::Context(store));
	}

	static String dir_of(const String& path)
	{
		auto pos = path.find_last_of('/');
		return(pos == String::npos ? String("") : path.substr(0, pos));
	}

	static String trap_text(const wasmtime::TrapError& error)
	{
		return(wasm_trace_collapse(String(error.message())));
	}

	// ---- guest memory access (pointer re-derived per call: it moves) ------

	String guest_write(u32 ptr, const String& data)
	{
		auto span = memory->data(ctx());
		if((size_t)ptr + data.size() > span.size())
			return("guest write out of bounds");
		memcpy(span.data() + ptr, data.data(), data.size());
		return("");
	}

	String guest_read(u32 ptr, u32 len, String& out)
	{
		auto span = memory->data(ctx());
		if((size_t)ptr + len > span.size())
			return("guest read out of bounds");
		out.assign((const char*)span.data() + ptr, len);
		return("");
	}

	// ---- calls -------------------------------------------------------------

	std::optional<wasmtime::Func> core_func(const String& name)
	{
		auto exported = core->get(ctx(), std::string_view(name));
		if(!exported)
			return(std::nullopt);
		if(auto* func = std::get_if<wasmtime::Func>(&*exported))
			return(*func);
		return(std::nullopt);
	}

	std::optional<wasmtime::Func> unit_func(size_t unit_index, const String& name)
	{
		auto exported = units[unit_index].instance->get(ctx(), std::string_view(name));
		if(!exported)
			return(std::nullopt);
		if(auto* func = std::get_if<wasmtime::Func>(&*exported))
			return(*func);
		return(std::nullopt);
	}

	String call_core(const String& name, std::vector<int32_t> argv, int32_t* result_out)
	{
		auto func = core_func(name);
		if(!func)
			return("core does not export " + name);
		std::vector<wasmtime::Val> args;
		for(auto value : argv)
			args.push_back(wasmtime::Val(value));
		auto result = func->call(ctx(), args);
		if(!result)
			return(trap_text(result.err()));
		auto values = result.ok();
		if(result_out)
			*result_out = values.empty() ? 0 : values[0].i32();
		return("");
	}

	// ---- symbol registry (core first, then units in load order) -----------

	std::optional<wasmtime::Func> resolve_func(const String& name)
	{
		if(auto func = core_func(name))
			return(func);
		for(size_t i = 0; i < units.size(); i++)
			if(auto func = unit_func(i, name))
				return(func);
		return(std::nullopt);
	}

	// data symbol: value of the exported i32 global + owning module's base
	// (core is non-PIC → base 0; PIC units export __memory_base-relative)
	bool resolve_data(const String& name, u32& address_out)
	{
		auto from_core = core->get(ctx(), std::string_view(name));
		if(from_core)
			if(auto* global = std::get_if<wasmtime::Global>(&*from_core))
			{
				address_out = (u32)global->get(ctx()).i32();
				return(true);
			}
		for(size_t i = 0; i < units.size(); i++)
		{
			auto exported = units[i].instance->get(ctx(), std::string_view(name));
			if(exported)
				if(auto* global = std::get_if<wasmtime::Global>(&*exported))
				{
					address_out = units[i].memory_base + (u32)global->get(ctx()).i32();
					return(true);
				}
		}
		return(false);
	}

	String place_funcref(const wasmtime::Func& func, u32& slot_out)
	{
		auto cx = ctx();
		if(table_next_free >= table->size(cx))
			return("funcref table headroom exhausted");
		auto set = table->set(cx, table_next_free, wasmtime::Val(std::optional<wasmtime::Func>(func)));
		if(!set)
			return("table set failed: " + String(set.err().message()));
		slot_out = table_next_free++;
		return("");
	}

	// ---- unit loading (the §6 sequence) ------------------------------------

	String load_unit(const String& source_path, size_t& unit_index_out)
	{
		auto known = units_by_source.find(source_path);
		if(known != units_by_source.end())
		{
			unit_index_out = known->second;
			return("");
		}

		String error;
		auto mod = worker.unit_module(source_path, error);
		if(!mod)
			return(error);
		// Compiling/deserializing a cold module is host work. Refresh the guest
		// watchdog before the first core call so that wall time cannot make the
		// otherwise harmless malloc/reloc sequence trap immediately.
		ctx().set_epoch_deadline(worker.cfg.epoch_deadline_ticks);
		if(mod->abi.version != abi_version)
			return(mod->wasm_path + ": uce.abi version " + std::to_string(mod->abi.version)
				+ " does not match core ABI " + std::to_string(abi_version));

		auto cx = ctx();
		auto& module = *mod->module;

		// base allocation
		u32 align = 1u << mod->dylink.mem_align;
		int32_t raw_base = 0;
		error = call_core("malloc", { (int32_t)(mod->dylink.mem_size + align) }, &raw_base);
		if(error != "")
			return(error);
		if(raw_base == 0)
			return("core malloc failed for unit data segment");
		u32 memory_base = ((u32)raw_base + (align - 1)) & ~(align - 1);
		u32 table_base = table_next_free;
		if(mod->dylink.table_size > table->size(cx) - table_next_free)
			return("funcref table headroom exhausted by " + source_path);
		table_next_free += mod->dylink.table_size;

		// import resolution
		std::vector<wasmtime::Extern> imports;
		std::vector<std::pair<String, wasmtime::Global>> deferred_got;
		for(auto import_type : module.imports())
		{
			String mod_name(import_type.module());
			String name(import_type.name());
			auto extern_type = wasmtime::ExternType::from_import(import_type);
			bool is_func_import = std::get_if<wasmtime::FuncType::Ref>(&extern_type) != 0;

			if(mod_name == "env" && name == "memory")
			{
				imports.push_back(*memory);
				continue;
			}
			if(mod_name == "env" && name == "__indirect_function_table")
			{
				imports.push_back(*table);
				continue;
			}
			if(mod_name == "env" && name == "__stack_pointer")
			{
				auto sp = core->get(cx, "__stack_pointer");
				if(!sp || !std::get_if<wasmtime::Global>(&*sp))
					return("core does not export __stack_pointer");
				imports.push_back(std::get<wasmtime::Global>(*sp));
				continue;
			}
			if(mod_name == "env" && (name == "__memory_base" || name == "__table_base"))
			{
				int32_t value = name == "__memory_base" ? (int32_t)memory_base : (int32_t)table_base;
				wasmtime::GlobalType global_type(wasmtime::ValType::i32(), false);
				auto global = wasmtime::Global::create(cx, global_type, wasmtime::Val(value));
				if(!global)
					return("global create failed: " + String(global.err().message()));
				imports.push_back(global.ok());
				continue;
			}
			if(mod_name == "env" && is_func_import)
			{
				auto func = resolve_func(name);
				if(!func)
					return(source_path + ": unresolved import env." + wasm_trace_demangle(name));
				imports.push_back(*func);
				continue;
			}
			if(mod_name == "GOT.mem")
			{
				wasmtime::GlobalType global_type(wasmtime::ValType::i32(), true);
				u32 address = 0;
				if(resolve_data(name, address))
				{
					auto global = wasmtime::Global::create(cx, global_type, wasmtime::Val((int32_t)address));
					if(!global)
						return("global create failed: " + String(global.err().message()));
					imports.push_back(global.ok());
				}
				else
				{
					// provisional; self-resolved from the unit's own export
					// (plus its memory base) after instantiation
					auto global = wasmtime::Global::create(cx, global_type, wasmtime::Val((int32_t)0));
					if(!global)
						return("global create failed: " + String(global.err().message()));
					deferred_got.push_back({ name, global.ok() });
					imports.push_back(deferred_got.back().second);
				}
				continue;
			}
			if(mod_name == "GOT.func")
			{
				auto func = resolve_func(name);
				if(!func)
					return(source_path + ": unresolved GOT.func." + wasm_trace_demangle(name));
				u32 slot = 0;
				error = place_funcref(*func, slot);
				if(error != "")
					return(error);
				wasmtime::GlobalType global_type(wasmtime::ValType::i32(), true);
				auto global = wasmtime::Global::create(cx, global_type, wasmtime::Val((int32_t)slot));
				if(!global)
					return("global create failed: " + String(global.err().message()));
				imports.push_back(global.ok());
				continue;
			}
			return(source_path + ": import policy violation: " + mod_name + "." + name);
		}

		auto created = wasmtime::Instance::create(cx, module, imports);
		if(!created)
			return(source_path + ": instantiation failed: " + trap_text(created.err()));

		LoadedUnit unit;
		unit.mod = mod;
		unit.instance.emplace(created.ok());
		unit.memory_base = memory_base;
		units.push_back(std::move(unit));
		size_t unit_index = units.size() - 1;
		units_by_source[source_path] = unit_index;
		unit_index_out = unit_index;

		// deferred GOT: the unit's own data exports are module-relative —
		// add this unit's memory base (Phase 0 FINDINGS erratum)
		for(auto& [name, got] : deferred_got)
		{
			auto own = units[unit_index].instance->get(cx, std::string_view(name));
			if(!own || !std::get_if<wasmtime::Global>(&*own))
				return(source_path + ": GOT.mem." + name + " defined neither by core nor by any unit");
			u32 offset = (u32)std::get<wasmtime::Global>(*own).get(cx).i32();
			auto set = got.set(cx, wasmtime::Val((int32_t)(memory_base + offset)));
			if(!set)
				return("GOT patch failed: " + String(set.err().message()));
		}

		// init sequence, then bind this unit's context to the request
		if(auto relocs = unit_func(unit_index, "__wasm_apply_data_relocs"))
		{
			auto result = relocs->call(ctx(), {});
			if(!result)
				return(trap_text(result.err()));
		}
		if(auto ctors = unit_func(unit_index, "__wasm_call_ctors"))
		{
			auto result = ctors->call(ctx(), {});
			if(!result)
				return(trap_text(result.err()));
		}
		if(auto set_request = unit_func(unit_index, "__uce_set_current_request"))
		{
			auto result = set_request->call(ctx(), { wasmtime::Val(request_ptr) });
			if(!result)
				return(trap_text(result.err()));
		}
		if(worker.cfg.verbose)
			fprintf(stderr, "[wasm] loaded %s (mem_base=%u table_base=%u)\n",
				source_path.c_str(), memory_base, table_base);
		// Exclude the rest of host-side loading as well. A genuine runaway loop
		// makes no loads, so it still trips the deadline.
		ctx().set_epoch_deadline(worker.cfg.epoch_deadline_ticks);
		return("");
	}

	// ---- component target resolution (host side of the membrane) ----------

	static String normalize_component_path(String name)
	{
		// mirrors component_normalize_path in compiler.cpp / core.cpp
		if(name.length() >= 4 && name.substr(name.length() - 4) == ".uce")
			return(name);
		return(name + ".uce");
	}

	static bool file_exists_host(const String& path)
	{
		struct stat st;
		return(stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode));
	}

	static bool dir_exists_host(const String& path)
	{
		struct stat st;
		return(stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode));
	}

	// Keep cwd host behavior local to this process but guard it with the same
	// write-root policy we use for file writes (plus a single parity fallback).
	String resolve_guest_cwd_set(const String& raw)
	{
		if(raw == "" || raw.find('\0') != String::npos)
			return("");

		String raw_target = raw;
		if(raw.rfind("/", 0) != 0)
		{
			String cwd = ::cwd_get();
			if(cwd == "")
				return("");
			raw_target = cwd + "/" + raw;
		}

		char resolved[PATH_MAX];
		if(!realpath(raw_target.c_str(), resolved))
			return("");
		String resolved_target(resolved);
		if(!dir_exists_host(resolved_target))
			return("");

		// Policy: allow only roots we already expose for writable filesystem access.
		std::vector<String> roots;
		roots.push_back(worker.cfg.site_root);
		for(auto& root : worker.cfg.write_roots)
			roots.push_back(root);
		for(auto& root : roots)
		{
			if(root == "")
				continue;
			char root_real[PATH_MAX];
			if(!realpath(root.c_str(), root_real))
				continue;
			String canonical_root(root_real);
			if(resolved_target == canonical_root)
				return(resolved_target);
			if(canonical_root != "/" && resolved_target.rfind(canonical_root + "/", 0) == 0)
				return(resolved_target);
		}

		// Parity/fallback: allow returning to the process start directory so
		// legacy behavior is not silently broken for existing native/cached flows.
		String start_directory = ::process_start_directory();
		if(start_directory != "" && resolved_target == start_directory)
			return(resolved_target);
		return("");
	}

	String resolve_source_path(const String& file_name, const String& current_unit)
	{
		std::vector<String> bases;
		if(file_name.rfind("/", 0) == 0)
			bases.push_back("");           // absolute target
		if(entry_dir != "")
			bases.push_back(entry_dir + "/");
		if(current_unit != "")
		{
			String current_dir = dir_of(current_unit);
			if(current_dir != "" && current_dir != entry_dir)
				bases.push_back(current_dir + "/");
		}
		bases.push_back(worker.cfg.site_root + "/");

		for(auto& base : bases)
		{
			std::vector<String> candidates;
			candidates.push_back(base + file_name);
			candidates.push_back(base + normalize_component_path(file_name));
			if(file_name.rfind("components/", 0) != 0 && base != "")
			{
				candidates.push_back(base + "components/" + file_name);
				candidates.push_back(base + "components/" + normalize_component_path(file_name));
			}
			for(auto& candidate : candidates)
				if(file_exists_host(candidate))
					return(candidate);
		}
		return("");
	}

	// guest file access policy: only inside the site tree, resolved against
	// the entry unit's directory first (the native cwd convention), then the
	// site root; containment checked on the canonicalized path
	String resolve_guest_file(const String& raw, const String& current_unit = "", bool allow_dir = false)
	{
		if(raw == "" || raw.find('\0') != String::npos)
			return("");
		std::vector<String> candidates;
		if(raw.rfind("/", 0) == 0)
			candidates.push_back(raw);
		else
		{
			String current_dir = current_unit != "" ? dir_of(current_unit) : String("");
			if(current_dir != "")
				candidates.push_back(current_dir + "/" + raw);
			if(entry_dir != "" && entry_dir != current_dir)
				candidates.push_back(entry_dir + "/" + raw);
			candidates.push_back(worker.cfg.site_root + "/" + raw);
		}
		// readable roots = the site tree plus the writable scratch dirs (a page
		// can read back what it is allowed to write, e.g. /tmp), canonicalized.
		std::vector<String> read_roots;
		read_roots.push_back(worker.cfg.site_root);
		for(auto& root : worker.cfg.write_roots)
			read_roots.push_back(root);
		std::vector<String> root_prefixes;
		for(auto& root : read_roots)
		{
			char root_real[4096];
			if(root != "" && realpath(root.c_str(), root_real))
				root_prefixes.push_back(String(root_real));
		}
		for(auto& candidate : candidates)
		{
			char resolved[4096];
			if(!realpath(candidate.c_str(), resolved))
				continue;
			String path(resolved);
			bool allowed = false;
			for(auto& prefix : root_prefixes)
				if(path == prefix || path.rfind(prefix + "/", 0) == 0)
				{
					allowed = true;
					break;
				}
			if(!allowed)
				continue;
			if(file_exists_host(path) || (allow_dir && dir_exists_host(path)))
				return(path);
		}
		return("");
	}

	// write membrane policy: resolve the target (absolute, or relative to the
	// current unit / site root) and allow it only if its parent directory
	// canonicalizes under one of the configured write roots (site tree + the
	// runtime scratch dirs). The file itself need not exist yet.
	String resolve_guest_write(const String& raw, const String& current_unit)
	{
		if(raw == "" || raw.find('\0') != String::npos)
			return("");
		String target;
		if(raw.rfind("/", 0) == 0)
			target = raw;
		else
		{
			String current_dir = current_unit != "" ? dir_of(current_unit) : String("");
			target = (current_dir != "" ? current_dir : worker.cfg.site_root) + "/" + raw;
		}
		String parent = dir_of(target);
		String base = parent.size() < target.size() ? target.substr(parent.size() + 1) : String("");
		if(base == "" || base == "." || base == "..")
			return("");
		char parent_real[4096];
		if(!realpath(parent.c_str(), parent_real))
			return("");
		String resolved_parent(parent_real);
		for(auto& root : worker.cfg.write_roots)
		{
			char root_real[4096];
			if(root == "" || !realpath(root.c_str(), root_real))
				continue;
			String root_prefix(root_real);
			if(resolved_parent == root_prefix || resolved_parent.rfind(root_prefix + "/", 0) == 0)
				return(resolved_parent + "/" + base);
		}
		return("");
	}

	static String sanitize_symbol_suffix(const String& raw)
	{
		// mirrors ascii_safe_name in functionlib.cpp
		String result;
		for(auto c : raw)
			if(isalnum((unsigned char)c) || c == '_')
				result.push_back(c);
		return(result);
	}

	// __uce_<base>[_<suffix>] for a handler spec like "component:CARD" / "render".
	static String handler_export_symbol(const String& handler)
	{
		String export_prefix = "export:";
		if(handler.rfind(export_prefix, 0) == 0)
			return(handler.substr(export_prefix.size()));
		auto colon = handler.find(":");
		String symbol = "__uce_" + (colon == String::npos ? handler : handler.substr(0, colon));
		if(colon != String::npos)
			symbol += "_" + sanitize_symbol_suffix(handler.substr(colon + 1));
		return(symbol);
	}

	// hostcall body: uce_host_component_resolve(unit, handler, current) → slot.
	// `handler` names the export ("render", "component:CARD", "cli",
	// "serve_http:named", "once") or is "exists" (probe only, loads nothing).
	int32_t component_resolve(const String& target, const String& handler, const String& current_unit, String& resolved_out)
	{
		auto probe_start = std::chrono::steady_clock::now();
		auto record_probe = [&]() {
			component_resolve_count += 1;
			component_resolve_total_us += (u64)std::chrono::duration_cast<std::chrono::microseconds>(
				std::chrono::steady_clock::now() - probe_start).count();
		};
		String file_name = target;
		if(file_name == "" && current_unit != "")
			file_name = current_unit;
		if(file_name == "")
		{
			record_probe();
			return(0);
		}

		String resolved = resolve_source_path(file_name, current_unit);
		if(resolved == "")
		{
			record_probe();
			return(0);
		}
		resolved_out = resolved;
		if(handler == "exists")
		{
			record_probe();
			return(1);
		}

		bool stale = compiler_unit_needs_recompile(context, resolved, 0);
		if(stale && compiler_request_can_serve_stale_artifact(context))
			compiler_prioritize_unit(context, resolved);
		if(!file_exists_host(worker.unit_wasm_path(resolved)) || (stale && !compiler_request_can_serve_stale_artifact(context) && !compiler_unit_compile_in_progress(context, resolved)))
			get_shared_unit(context, resolved);

		size_t unit_index = 0;
		String error = load_unit(resolved, unit_index);
		if(error != "")
		{
			fprintf(stderr, "[wasm] component load failed: %s\n", error.c_str());
			record_probe();
			return(0);
		}
		String symbol = handler_export_symbol(handler);
		String slot_key = resolved + ":" + symbol;
		auto cached = handler_slots.find(slot_key);
		if(cached != handler_slots.end())
		{
			record_probe();
			return((int32_t)cached->second);
		}
		auto handler_fn = unit_func(unit_index, symbol);
		if(!handler_fn)
		{
			// ONCE is optional per unit; a missing __uce_once is not an error
			if(handler != "once")
				fprintf(stderr, "[wasm] %s does not export %s\n", resolved.c_str(), symbol.c_str());
			record_probe();
			return(0);
		}
		u32 slot = 0;
		error = place_funcref(*handler_fn, slot);
		if(error != "")
		{
			fprintf(stderr, "[wasm] %s\n", error.c_str());
			record_probe();
			return(0);
		}
		handler_slots[slot_key] = slot;
		record_probe();
		return((int32_t)slot);
	}

	String run_task_callback(u64 callback_id)
	{
		auto runner = core_func("uce_wasm_task_run");
		if(!runner)
			return("core does not export uce_wasm_task_run");
		auto result = runner->call(ctx(), { wasmtime::Val((int64_t)callback_id) });
		if(!result)
			return(trap_text(result.err()));
		return("");
	}

	// ---- host imports for the core -----------------------------------------

	wasmtime::Extern make_host_import(wasmtime::Store::Context cx, const String& mod, const String& name, const wasmtime::FuncType& func_type)
	{
		using namespace wasmtime;
		WasmWorkspace* self = this;

		auto add = [&](auto&& callback) -> Extern {
			Func func(cx, func_type, callback);
			host_funcs.push_back(func);
			return(host_funcs.back());
		};

		// Hostcall blocklist (UCE_HOSTCALL_BLOCKLIST): a sysadmin-disabled hostcall
		// resolves to a trap stub instead of its real implementation, so a unit
		// invoking it fails at runtime into the configurable error page. The
		// decision is made once per import at workspace birth — no per-call cost,
		// and zero cost when nothing is blocked. A small core set stays exempt so
		// the runtime itself cannot be bricked.
		if(mod == "env" && !worker.cfg.hostcall_blocklist.empty() && name.rfind("uce_host_", 0) == 0)
		{
			static const std::set<String> non_blockable = { "component_resolve" };
			String bare = name.substr(9);
			if(worker.cfg.hostcall_blocklist.count(bare) && !non_blockable.count(bare))
			{
				std::string blocked(name);
				return(add([blocked](Caller, Span<const Val>, Span<Val>) -> Result<std::monostate, Trap> {
					return(Trap("UCE_POLICY_BLOCKED:" + blocked));
				}));
			}
		}

		if(mod == "env" && name == "uce_host_time")
			return(add([](Caller, Span<const Val>, Span<Val> results) -> Result<std::monostate, Trap> {
				results[0] = Val((int64_t)::time(0));
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_time_precise")
			return(add([](Caller, Span<const Val>, Span<Val> results) -> Result<std::monostate, Trap> {
				results[0] = Val(time_precise());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_request_perf")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				DValue response;
				if(self->request_perf.active)
				{
					f64 now = time_precise();
					response["worker_pid"] = (f64)self->request_perf.worker_pid;
					response["parent_pid"] = (f64)self->request_perf.parent_pid;
					response["request_count"] = (f64)self->request_perf.request_count;
					if(self->request_perf.time_start > 0 && self->request_perf.time_init > 0)
						response["accept_us"] = (f64)((self->request_perf.time_start - self->request_perf.time_init) * 1000000.0);
					if(self->request_perf.time_start > 0)
						response["running_us"] = (f64)((now - self->request_perf.time_start) * 1000000.0);
					if(self->request_perf.time_init > 0)
						response["total_us"] = (f64)((now - self->request_perf.time_init) * 1000000.0);
					if(self->workspace_birth_us > 0)
						response["workspace_birth_us"] = (f64)self->workspace_birth_us;
				}
				String encoded = ucb_encode(response);
				u32 cap = (u32)args[3].i32();
				int32_t buf = args[2].i32();
				if(buf != 0 && cap >= encoded.size())
					self->hostcall_write(buf, encoded);
				results[0] = Val((int32_t)encoded.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_env")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String key, value;
				if(self->hostcall_read(args[0].i32(), args[1].i32(), key) == "")
					if(const char* raw = getenv(key.c_str()))
						value = raw;
				u32 cap = (u32)args[3].i32();
				if(value.size() && cap >= value.size())
					self->hostcall_write(args[2].i32(), value);
				results[0] = Val((int32_t)value.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_random")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				u32 len = (u32)args[1].i32();
				String bytes(len, 0);
				FILE* urandom = fopen("/dev/urandom", "rb");
				if(urandom)
				{
					size_t got = fread(&bytes[0], 1, len, urandom);
					fclose(urandom);
					bytes.resize(got);
				}
				self->hostcall_write(args[0].i32(), bytes);
				results[0] = Val((int32_t)bytes.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_sha256")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String in; self->hostcall_read(args[0].i32(), args[1].i32(), in); String out=sha256_native(in); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_sha256_hex")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String in; self->hostcall_read(args[0].i32(), args[1].i32(), in); String out=sha256_hex_native(in); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_hmac_sha256")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String key,in; self->hostcall_read(args[0].i32(), args[1].i32(), key); self->hostcall_read(args[2].i32(), args[3].i32(), in); String out=hmac_sha256_native(key,in); u32 cap=(u32)args[5].i32(); int32_t buf=args[4].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_hmac_sha256_hex")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String key,in; self->hostcall_read(args[0].i32(), args[1].i32(), key); self->hostcall_read(args[2].i32(), args[3].i32(), in); String out=hmac_sha256_hex_native(key,in); u32 cap=(u32)args[5].i32(); int32_t buf=args[4].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_base64_encode")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String in; self->hostcall_read(args[0].i32(), args[1].i32(), in); String out=base64_encode(in); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_base64_decode")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String in; self->hostcall_read(args[0].i32(), args[1].i32(), in); bool ok=false; String out=base64_decode(in, ok); if(!ok) out=""; u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_crypto_equal")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String a,b; self->hostcall_read(args[0].i32(), args[1].i32(), a); self->hostcall_read(args[2].i32(), args[3].i32(), b); results[0]=Val((int32_t)(crypto_equal_native(a,b)?1:0)); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_password_hash")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String password; self->hostcall_read(args[0].i32(), args[1].i32(), password); String out=password_hash_native(password); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_password_verify")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String password,encoded; self->hostcall_read(args[0].i32(), args[1].i32(), password); self->hostcall_read(args[2].i32(), args[3].i32(), encoded); bool valid=password_verify_native(password,encoded); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0]=Val((int32_t)(valid?1:0)); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_password_needs_rehash")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); results[0]=Val((int32_t)(password_needs_rehash_native(encoded)?1:0)); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_log")
			return(add([self](Caller, Span<const Val> args, Span<Val>) -> Result<std::monostate, Trap> {
				String text;
				self->hostcall_read(args[1].i32(), args[2].i32(), text);
				fprintf(stderr, "[guest log %d] %.*s\n", args[0].i32(), (int)text.size(), text.data());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_shell_exec")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String cmd;
				self->hostcall_read(args[0].i32(), args[1].i32(), cmd);
				u32 cap = (u32)args[3].i32();
				int32_t buf = args[2].i32();
				String out;
				String stage_key = "shell:" + cmd;
				if(!self->hostcall_staged(stage_key, out))
				{
					out = ::shell_exec(cmd);
					if(buf == 0)
						self->hostcall_stage(stage_key, out);
				}
				if(buf != 0 && cap >= out.size())
					self->hostcall_write(buf, out);
				caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
				results[0] = Val((int32_t)out.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_http_request")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); String out; String stage_key="http:"+encoded;
				if(!self->hostcall_staged(stage_key,out)) { DValue req,response; String err; if(ucb_decode(encoded,req,&err)) response=uce_http_request_value(req); else response["error"]="http_request decode failed: "+err; out=ucb_encode(response); if(buf==0) self->hostcall_stage(stage_key,out); }
				if(buf&&cap>=out.size()) self->hostcall_write(buf,out); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0]=Val((int32_t)out.size()); return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_http_request_async")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); DValue req; String err; u64 id=0; if(ucb_decode(encoded,req,&err)) id=uce_http_spawn_spec(req); results[0]=Val((int64_t)id); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_shell_exec_dv")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded);
				u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32();
				String out; String stage_key="shell_dv:"+encoded;
				if(!self->hostcall_staged(stage_key,out)) { DValue spec, response; String err; if(ucb_decode(encoded,spec,&err)) response=uce_shell_exec_spec(spec); else response["error"]="shell_exec spec decode failed: "+err; out=ucb_encode(response); if(buf==0) self->hostcall_stage(stage_key,out); }
				if(buf&&cap>=out.size()) self->hostcall_write(buf,out);
				caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
				results[0]=Val((int32_t)out.size()); return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_shell_spawn")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); DValue spec; String err; u64 id=0; if(ucb_decode(encoded,spec,&err)) id=uce_shell_spawn_spec(spec); results[0]=Val((int64_t)id); return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_job_status")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String out=ucb_encode(uce_job_status_value((u64)args[0].i64())); u32 cap=(u32)args[2].i32(); int32_t buf=args[1].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_job_result")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String out=ucb_encode(uce_job_result_value((u64)args[0].i64(), 100)); u32 cap=(u32)args[2].i32(); int32_t buf=args[1].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_job_await")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { u64 timeout=std::min<u64>((u64)args[1].i64(), 30000); String out=ucb_encode(uce_job_result_value((u64)args[0].i64(), timeout)); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_job_cancel")
			return(add([](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { results[0]=Val((int32_t)(uce_job_cancel_value((u64)args[0].i64())?1:0)); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_path_real")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path;
				self->hostcall_read(args[0].i32(), args[1].i32(), path);
				String resolved = ::path_real(path);
				u32 cap = (u32)args[3].i32();
				int32_t buf = args[2].i32();
				if(buf != 0 && cap >= resolved.size())
					self->hostcall_write(buf, resolved);
				results[0] = Val((int32_t)resolved.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_path_is_within")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path, root;
				self->hostcall_read(args[0].i32(), args[1].i32(), path);
				self->hostcall_read(args[2].i32(), args[3].i32(), root);
				results[0] = Val(::path_is_within(path, root) ? (int32_t)1 : (int32_t)0);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_cwd_get")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String cwd = ::cwd_get();
				u32 cap = (u32)args[1].i32();
				int32_t buf = args[0].i32();
				if(buf != 0 && cap >= cwd.size())
					self->hostcall_write(buf, cwd);
				results[0] = Val((int32_t)cwd.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_cwd_set")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path;
				self->hostcall_read(args[0].i32(), args[1].i32(), path);
				String resolved = self->resolve_guest_cwd_set(path);
				results[0] = Val(::chdir(resolved.c_str()) == 0 ? (int32_t)1 : (int32_t)0);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_process_start_directory")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String cwd = ::process_start_directory();
				u32 cap = (u32)args[1].i32();
				int32_t buf = args[0].i32();
				if(buf != 0 && cap >= cwd.size())
					self->hostcall_write(buf, cwd);
				results[0] = Val((int32_t)cwd.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_last_trap_trace")
			return(add([](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				(void)args;
				results[0] = Val((int32_t)0);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_exists")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path, current;
				self->hostcall_read(args[0].i32(), args[1].i32(), path);
				self->hostcall_read(args[2].i32(), args[3].i32(), current);
				String resolved = self->resolve_guest_file(path, current);
				if(self->worker.cfg.verbose)
					fprintf(stderr, "[wasm] file_exists(%s, current=%s) -> %s\n", path.c_str(), current.c_str(), resolved.c_str());
				results[0] = Val(resolved != "" ? (int32_t)1 : (int32_t)0);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_mkdir")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path, current;
				self->hostcall_read(args[0].i32(), args[1].i32(), path);
				self->hostcall_read(args[2].i32(), args[3].i32(), current);
				String resolved = self->resolve_guest_write(path, current);
				int ok = 0;
				if(resolved != "")
					ok = (::mkdir(resolved.c_str(), 0777) == 0 || errno == EEXIST) ? 1 : 0;
				results[0] = Val((int32_t)ok);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_mtime")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path, current;
				self->hostcall_read(args[0].i32(), args[1].i32(), path);
				self->hostcall_read(args[2].i32(), args[3].i32(), current);
				String resolved = self->resolve_guest_file(path, current);
				int64_t mtime = 0;
				struct stat st;
				if(resolved != "" && stat(resolved.c_str(), &st) == 0)
					mtime = (int64_t)st.st_mtime;
				results[0] = Val((int64_t)mtime);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_read")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path, current;
				self->hostcall_read(args[0].i32(), args[1].i32(), path);
				self->hostcall_read(args[2].i32(), args[3].i32(), current);
				String stage_key = "file_read:" + path + "\0" + current;
				String content;
				if(!self->hostcall_staged(stage_key, content))
				{
					String resolved = self->resolve_guest_file(path, current);
					content = resolved == "" ? String("") : ::file_get_contents(resolved);
					self->hostcall_stage(stage_key, content);
				}
				u32 cap = (u32)args[5].i32();
				int32_t buf = args[4].i32();
				// length-query convention: no copy unless the buffer fits
				if(buf != 0 && cap >= content.size())
					self->hostcall_write(buf, content);
				results[0] = Val((int32_t)content.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_list")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path, current;
				self->hostcall_read(args[0].i32(), args[1].i32(), path);
				self->hostcall_read(args[2].i32(), args[3].i32(), current);
				String resolved = self->resolve_guest_file(path, current, true /*allow_dir*/);
				String listing;
				if(resolved != "")
				{
					std::vector<String> names;
					if(DIR* d = opendir(resolved.c_str()))
					{
						while(struct dirent* e = readdir(d))
						{
							String n = e->d_name;
							if(n != "." && n != "..")
								names.push_back(n);
						}
						closedir(d);
					}
					// match the native ls -1 convention: bare names, sorted
					std::sort(names.begin(), names.end());
					listing = join(names, "\n");
				}
				u32 cap = (u32)args[5].i32();
				int32_t buf = args[4].i32();
				// length-query convention: no copy unless the buffer fits
				if(buf != 0 && cap >= listing.size())
					self->hostcall_write(buf, listing);
				results[0] = Val((int32_t)listing.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_write")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path, current, content;
				self->hostcall_read(args[0].i32(), args[1].i32(), path);
				self->hostcall_read(args[2].i32(), args[3].i32(), current);
				self->hostcall_read(args[4].i32(), args[5].i32(), content);
				bool append = args[6].i32() != 0;
				String resolved = self->resolve_guest_write(path, current);
				bool ok = false;
				if(resolved != "")
					ok = append ? file_append(resolved, content) : file_put_contents(resolved, content);
				else if(self->worker.cfg.verbose)
					fprintf(stderr, "[wasm] file_write denied: %s\n", path.c_str());
				results[0] = Val(ok ? (int32_t)1 : (int32_t)0);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_open")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path, current, mode;
				self->hostcall_read(args[0].i32(), args[1].i32(), path);
				self->hostcall_read(args[2].i32(), args[3].i32(), current);
				self->hostcall_read(args[4].i32(), args[5].i32(), mode);
				String resolved;
				int flags = O_RDONLY;
				int lock_type = LOCK_SH;
				bool writable = false;
				bool truncate_after_lock = false;
				if(mode == "r")
					resolved = self->resolve_guest_file(path, current);
				else if(mode == "w")
				{
					resolved = self->resolve_guest_write(path, current);
					flags = O_RDWR | O_CREAT;
					lock_type = LOCK_EX;
					writable = true;
					truncate_after_lock = true;
				}
				else if(mode == "a")
				{
					resolved = self->resolve_guest_write(path, current);
					flags = O_RDWR | O_CREAT | O_APPEND;
					lock_type = LOCK_EX;
					writable = true;
				}
				else if(mode == "r+")
				{
					resolved = self->resolve_guest_write(path, current);
					flags = O_RDWR;
					lock_type = LOCK_EX;
					writable = true;
				}
				uint64_t handle = 0;
				if(resolved != "")
				{
					int fd = wasm_open_locked_file(resolved, flags, lock_type, truncate_after_lock);
					if(fd >= 0)
					{
						if(mode == "a")
							lseek(fd, 0, SEEK_END);
						self->file_handles.push_back({fd, writable});
						handle = self->file_handles.size();
					}
				}
				results[0] = Val((int64_t)handle);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_handle_read")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				u64 handle = (u64)args[0].i64();
				u64 len = (u64)args[1].i64();
				u32 cap = (u32)args[3].i32();
				int32_t buf = args[2].i32();
				String out;
				String stage_key = "file_handle_read:" + std::to_string(handle) + ":" + std::to_string(len);
				if(!self->hostcall_staged(stage_key, out))
				{
					if(handle >= 1 && handle <= self->file_handles.size())
					{
						int fd = self->file_handles[(size_t)handle - 1].fd;
						if(fd >= 0 && len > 0)
						{
							out.resize((size_t)std::min<u64>(len, 16ull * 1024ull * 1024ull));
							ssize_t n = read(fd, &out[0], out.size());
							out.resize(n > 0 ? (size_t)n : 0);
						}
					}
					if(buf == 0) self->hostcall_stage(stage_key, out);
				}
				if(buf != 0 && cap >= out.size()) self->hostcall_write(buf, out);
				results[0] = Val((int32_t)out.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_handle_pread")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				u64 handle = (u64)args[0].i64();
				u64 offset = (u64)args[1].i64();
				u64 len = (u64)args[2].i64();
				u32 cap = (u32)args[4].i32();
				int32_t buf = args[3].i32();
				String out;
				String stage_key = "file_handle_pread:" + std::to_string(handle) + ":" + std::to_string(offset) + ":" + std::to_string(len);
				if(!self->hostcall_staged(stage_key, out))
				{
					if(handle >= 1 && handle <= self->file_handles.size())
					{
						int fd = self->file_handles[(size_t)handle - 1].fd;
						if(fd >= 0 && len > 0)
						{
							out.resize((size_t)std::min<u64>(len, 16ull * 1024ull * 1024ull));
							ssize_t n = pread(fd, &out[0], out.size(), (off_t)offset);
							out.resize(n > 0 ? (size_t)n : 0);
						}
					}
					if(buf == 0) self->hostcall_stage(stage_key, out);
				}
				if(buf != 0 && cap >= out.size()) self->hostcall_write(buf, out);
				results[0] = Val((int32_t)out.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_handle_write")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String data; self->hostcall_read(args[1].i32(), args[2].i32(), data);
				u64 handle = (u64)args[0].i64(); u64 written = 0;
				if(handle >= 1 && handle <= self->file_handles.size())
				{
					auto& h = self->file_handles[(size_t)handle - 1];
					if(h.fd >= 0 && h.writable) wasm_fd_write_all(h.fd, data.data(), data.size(), &written);
				}
				results[0] = Val((int64_t)written);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_handle_pwrite")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String data; self->hostcall_read(args[2].i32(), args[3].i32(), data);
				u64 handle = (u64)args[0].i64(); u64 offset = (u64)args[1].i64(); u64 written = 0;
				if(handle >= 1 && handle <= self->file_handles.size())
				{
					auto& h = self->file_handles[(size_t)handle - 1];
					if(h.fd >= 0 && h.writable)
					{
						while(written < data.size())
						{
							ssize_t n = pwrite(h.fd, data.data() + written, data.size() - written, (off_t)(offset + written));
							if(n < 0 && errno == EINTR) continue;
							if(n <= 0) break;
							written += (u64)n;
						}
					}
				}
				results[0] = Val((int64_t)written);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_handle_seek")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				u64 handle = (u64)args[0].i64(); s64 pos = -1;
				if(handle >= 1 && handle <= self->file_handles.size())
				{
					int fd = self->file_handles[(size_t)handle - 1].fd;
					if(fd >= 0) pos = (s64)lseek(fd, (off_t)args[1].i64(), args[2].i32());
				}
				results[0] = Val((int64_t)pos);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_handle_tell")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				u64 handle = (u64)args[0].i64(); s64 pos = -1;
				if(handle >= 1 && handle <= self->file_handles.size())
				{
					int fd = self->file_handles[(size_t)handle - 1].fd;
					if(fd >= 0) pos = (s64)lseek(fd, 0, SEEK_CUR);
				}
				results[0] = Val((int64_t)pos);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_handle_close")
			return(add([self](Caller, Span<const Val> args, Span<Val>) -> Result<std::monostate, Trap> {
				u64 handle = (u64)args[0].i64();
				if(handle >= 1 && handle <= self->file_handles.size())
				{
					auto& h = self->file_handles[(size_t)handle - 1];
					if(h.fd >= 0) { flock(h.fd, LOCK_UN); close(h.fd); h.fd = -1; }
				}
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_stat")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path, current; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current);
				String resolved = self->resolve_guest_file(path, current, true); DValue r; struct stat st;
				r["exists"].set_bool(resolved != "" && lstat(resolved.c_str(), &st) == 0);
				if(r["exists"].to_bool()) { r["size"]=(f64)st.st_size; r["mtime"]=(f64)st.st_mtime; r["ctime"]=(f64)st.st_ctime; r["mode"]=(f64)(st.st_mode & 07777); r["is_dir"].set_bool(S_ISDIR(st.st_mode)); r["is_file"].set_bool(S_ISREG(st.st_mode)); r["is_symlink"].set_bool(S_ISLNK(st.st_mode)); }
				else { r["size"]=(f64)0; r["mtime"]=(f64)0; r["ctime"]=(f64)0; r["mode"]=(f64)0; r["is_dir"].set_bool(false); r["is_file"].set_bool(false); r["is_symlink"].set_bool(false); }
				String out = ucb_encode(r); u32 cap=(u32)args[5].i32(); int32_t buf=args[4].i32(); if(buf && cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_dir_list")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String path, current; self->hostcall_read(args[0].i32(), args[1].i32(), path); self->hostcall_read(args[2].i32(), args[3].i32(), current);
				String resolved = self->resolve_guest_file(path, current, true); DValue list; list.set_array();
				if(resolved != "") { std::vector<String> names; if(DIR* d=opendir(resolved.c_str())) { while(struct dirent* e=readdir(d)) { String n=e->d_name; if(n!="."&&n!="..") names.push_back(n); } closedir(d); } std::sort(names.begin(), names.end()); for(auto& n:names) { String p=resolved+"/"+n; struct stat st; DValue item; item["name"]=n; if(lstat(p.c_str(), &st)==0) { item["size"]=(f64)st.st_size; item["mtime"]=(f64)st.st_mtime; item["type"]=S_ISDIR(st.st_mode)?"dir":S_ISLNK(st.st_mode)?"symlink":S_ISREG(st.st_mode)?"file":"other"; } list.push(item); } }
				String out=ucb_encode(list); u32 cap=(u32)args[5].i32(); int32_t buf=args[4].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_file_rename")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String from,to,current; self->hostcall_read(args[0].i32(),args[1].i32(),from); self->hostcall_read(args[2].i32(),args[3].i32(),to); self->hostcall_read(args[4].i32(),args[5].i32(),current); String rf=self->resolve_guest_write(from,current), rt=self->resolve_guest_write(to,current); results[0]=Val((int32_t)(rf!=""&&rt!=""&&rename(rf.c_str(),rt.c_str())==0)); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_file_copy")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String from,to,current; self->hostcall_read(args[0].i32(),args[1].i32(),from); self->hostcall_read(args[2].i32(),args[3].i32(),to); self->hostcall_read(args[4].i32(),args[5].i32(),current); String rf=self->resolve_guest_file(from,current), rt=self->resolve_guest_write(to,current); bool ok=false; if(rf!=""&&rt!="") { std::ifstream in(rf, std::ios::binary); std::ofstream out(rt, std::ios::binary|std::ios::trunc); out<<in.rdbuf(); struct stat st; if(in&&out) { ok=true; if(stat(rf.c_str(),&st)==0) chmod(rt.c_str(), st.st_mode & 07777); } } results[0]=Val((int32_t)ok); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_file_truncate")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String path,current; self->hostcall_read(args[0].i32(),args[1].i32(),path); self->hostcall_read(args[2].i32(),args[3].i32(),current); String r=self->resolve_guest_write(path,current); results[0]=Val((int32_t)(r!=""&&truncate(r.c_str(),(off_t)args[4].i64())==0)); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_dir_remove")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String path,current; self->hostcall_read(args[0].i32(),args[1].i32(),path); self->hostcall_read(args[2].i32(),args[3].i32(),current); String r=self->resolve_guest_write(path,current); bool rec=args[4].i32()!=0; bool ok=false; if(r!="") { if(rec) ok=std::filesystem::remove_all(r)>0; else ok=::rmdir(r.c_str())==0; } results[0]=Val((int32_t)ok); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_file_temp")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String prefix,current; self->hostcall_read(args[0].i32(),args[1].i32(),prefix); self->hostcall_read(args[2].i32(),args[3].i32(),current); u32 cap=(u32)args[5].i32(); int32_t buf=args[4].i32(); String out; String stage_key="file_temp:"+prefix+"\0"+current; if(!self->hostcall_staged(stage_key,out)) { if(prefix=="") prefix="/tmp/uce-temp"; String templ=self->resolve_guest_write(prefix+"XXXXXX",current); if(templ!="") { std::vector<char> t(templ.begin(), templ.end()); t.push_back(0); int fd=mkstemp(t.data()); if(fd>=0) { close(fd); out=t.data(); } } if(buf==0) self->hostcall_stage(stage_key,out); } if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_file_chmod")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String path,current; self->hostcall_read(args[0].i32(),args[1].i32(),path); self->hostcall_read(args[2].i32(),args[3].i32(),current); String r=self->resolve_guest_write(path,current); results[0]=Val((int32_t)(r!=""&&chmod(r.c_str(),(mode_t)args[4].i32())==0)); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_file_symlink")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String target,linkpath,current; self->hostcall_read(args[0].i32(),args[1].i32(),target); self->hostcall_read(args[2].i32(),args[3].i32(),linkpath); self->hostcall_read(args[4].i32(),args[5].i32(),current); String rt=self->resolve_guest_file(target,current), rl=self->resolve_guest_write(linkpath,current); results[0]=Val((int32_t)(rt!=""&&rl!=""&&symlink(rt.c_str(),rl.c_str())==0)); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_file_fsync")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { u64 handle=(u64)args[0].i64(); bool ok=false; if(handle>=1&&handle<=self->file_handles.size()) { int fd=self->file_handles[(size_t)handle-1].fd; ok=fd>=0&&fsync(fd)==0; } results[0]=Val((int32_t)ok); return(std::monostate()); }));
		if(mod == "env" && name == "uce_host_zip")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String encoded;
				self->hostcall_read(args[0].i32(), args[1].i32(), encoded);
				u32 cap = (u32)args[3].i32();
				int32_t buf = args[2].i32();
				String out;
				String stage_key = "zip:" + encoded;
				if(!self->hostcall_staged(stage_key, out))
				{
					DValue request, response;
					String decode_error;
					try
					{
						if(ucb_decode(encoded, request, &decode_error))
						{
							String op = request["op"].to_string();
							if(op == "list")
							{
								String path = self->resolve_guest_file(request["path"].to_string());
								if(path == "") throw std::runtime_error("zip_list: path is outside wasm file policy");
								response["result"] = zip_list(path);
							}
							else if(op == "read")
							{
								String path = self->resolve_guest_file(request["path"].to_string());
								if(path == "") throw std::runtime_error("zip_read: path is outside wasm file policy");
								response["result"] = zip_read(path, request["entry"].to_string());
							}
							else if(op == "create")
							{
								String path = self->resolve_guest_write(request["path"].to_string(), "");
								if(path == "") throw std::runtime_error("zip_create: path is outside wasm file policy");
								DValue* entries = request.key("entries");
								response["ok"].set_bool(zip_create(path, entries ? *entries : DValue()));
							}
							else if(op == "extract")
							{
								String path = self->resolve_guest_file(request["path"].to_string());
								String destination = self->resolve_guest_write(request["destination"].to_string(), "");
								if(path == "" || destination == "") throw std::runtime_error("zip_extract: path is outside wasm file policy");
								response["ok"].set_bool(zip_extract(path, destination));
							}
							else if(op == "gz_compress")
								response["result"] = gz_compress(request["src"].to_string());
							else if(op == "gz_uncompress")
								response["result"] = gz_uncompress(request["src"].to_string());
						}
					}
					catch(const std::exception& e)
					{
						response["error"] = e.what();
					}
					out = ucb_encode(response);
					if(buf == 0)
						self->hostcall_stage(stage_key, out);
				}
				if(buf != 0 && cap >= out.size())
					self->hostcall_write(buf, out);
				results[0] = Val((int32_t)out.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_units")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String encoded;
				self->hostcall_read(args[0].i32(), args[1].i32(), encoded);
				u32 cap = (u32)args[3].i32();
				int32_t buf = args[2].i32();
				String out;
				String stage_key = "units:" + encoded;
				if(!self->hostcall_staged(stage_key, out))
				{
					DValue request, response;
					String decode_error;
					try
					{
						if(ucb_decode(encoded, request, &decode_error))
						{
							String op = request["op"].to_string();
							if(op == "info")
								response["result"] = unit_info(request["path"].to_string());
							else if(op == "list")
							{
								StringList paths = units_list();
								for(auto& path : paths)
								{
									DValue item;
									item = path;
									response["result"].push(item);
								}
							}
							else if(op == "compile")
								response["ok"].set_bool(unit_compile(request["path"].to_string()));
							else if(op == "call")
							{
								DValue* param = request.key("param");
								ob_start();
								DValue* result = unit_call(request["file"].to_string(), request["function"].to_string(), param);
								response["output"] = ob_get_close();
								if(result)
									response["result"] = *result;
							}
						}
					}
					catch(const std::exception& e)
					{
						response["error"] = e.what();
					}
					out = ucb_encode(response);
					if(buf == 0)
						self->hostcall_stage(stage_key, out);
				}
				if(buf != 0 && cap >= out.size())
					self->hostcall_write(buf, out);
				caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
				results[0] = Val((int32_t)out.size());
				return(std::monostate());
			}));
#ifdef UCE_WASM_HOST_CONNECTORS
		if(mod == "env" && name == "uce_host_sqlite")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				// {op,handle,path,query,params} in → result out. The native
				// connector runs host-side; connections live in the workspace
				// handle table (handle = 1-based index).
				String encoded;
				self->hostcall_read(args[0].i32(), args[1].i32(), encoded);
				u32 cap = (u32)args[3].i32();
				int32_t buf = args[2].i32();
				String out;
				String stage_key = "sqlite:" + encoded;
				// run the op once across the length-query + fetch pair
				if(!self->hostcall_staged(stage_key, out))
				{
					DValue request, response;
					String decode_error;
					if(ucb_decode(encoded, request, &decode_error))
					{
						String op = request["op"].to_string();
						if(op == "connect")
						{
							SQLite* db = new SQLite();
							db->connect(request["path"].to_string());
							u64 handle = 0;
							if(db->connection)
							{
								self->sqlite_handles.push_back(db);
								handle = self->sqlite_handles.size();
							}
							response["handle"] = (f64)handle;
							response["error_code"] = (f64)db->error_code;
							response["statement_info"] = db->error();
							if(handle == 0)
								delete db;
						}
						else
						{
							u64 handle = request["handle"].to_u64();
							SQLite* db = (handle >= 1 && handle <= self->sqlite_handles.size())
								? self->sqlite_handles[(size_t)handle - 1] : 0;
							if(op == "query" && db)
							{
								StringMap params;
								DValue* p = request.key("params");
								if(p)
									p->each([&](const DValue& value, String key) { params[key] = value.to_string(); });
								response["result"] = db->query(request["query"].to_string(), params);
								response["insert_id"] = (f64)db->insert_id;
								response["affected"] = (f64)db->affected_rows;
								response["error_code"] = (f64)db->error_code;
								response["statement_info"] = db->error();
							}
							else if(op == "disconnect" && db)
							{
								delete db;
								self->sqlite_handles[(size_t)handle - 1] = 0;
							}
						}
					}
					out = ucb_encode(response);
					if(buf == 0)
						self->hostcall_stage(stage_key, out);
				}
				if(buf != 0 && cap >= out.size())
					self->hostcall_write(buf, out);
				results[0] = Val((int32_t)out.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_memcache_command")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String command;
				self->hostcall_read(args[1].i32(), args[2].i32(), command);
				String key = "memcache:" + std::to_string((u64)args[0].i64()) + ":" + command;
				u32 cap = (u32)args[4].i32();
				int32_t buf = args[3].i32();
				String out;
				if(buf != 0 && self->staged_memcache_key == key)
				{
					out = self->staged_memcache_result;
					self->staged_memcache_key = "";
					self->staged_memcache_result = "";
				}
				else
				{
					::socket_write((u64)args[0].i64(), command + "\r\n");
					out = ::socket_read((u64)args[0].i64());
					if(buf == 0)
					{
						self->staged_memcache_key = key;
						self->staged_memcache_result = out;
					}
				}
				if(buf != 0 && cap >= out.size())
					self->hostcall_write(buf, out);
				caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
				results[0] = Val((int32_t)out.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_mysql")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String encoded;
				self->hostcall_read(args[0].i32(), args[1].i32(), encoded);
				u32 cap = (u32)args[3].i32();
				int32_t buf = args[2].i32();
				String out;
				String stage_key = "mysql:" + encoded;
				if(!self->hostcall_staged(stage_key, out))
				{
					DValue request, response;
					String decode_error;
					if(ucb_decode(encoded, request, &decode_error))
					{
						String op = request["op"].to_string();
						if(op == "connect")
						{
							String host = request["host"].to_string();
							String username = request["username"].to_string();
							String password = request["password"].to_string();
							MySQL* db = 0;
							for(auto* pooled : self->mysql_request_pool)
								if(pooled && pooled->connection && pooled->request_host == host && pooled->request_username == username && pooled->request_password == password)
								{
									db = pooled;
									break;
								}
							bool ok = db != 0;
							if(!db)
							{
								db = new MySQL();
								db->request_pooled = true;
								db->request_host = host;
								db->request_username = username;
								db->request_password = password;
								ok = db->connect(host, username, password);
								if(ok && db->connection)
									self->mysql_request_pool.push_back(db);
							}
							u64 handle = 0;
							if(ok && db->connection)
							{
								db->request_leases++;
								self->mysql_handles.push_back(db);
								handle = self->mysql_handles.size();
							}
							response["handle"] = (f64)handle;
							response["error_code"] = (f64)db->_preload_next_error_code;
							response["statement_info"] = db->error();
							if(handle == 0)
								delete db;
						}
						else if(op == "escape")
						{
							String quote = request["quote_char"].to_string();
							response["result"] = mysql_escape(request["raw"].to_string(), quote.size() ? quote[0] : 0);
						}
						else
						{
							u64 handle = request["handle"].to_u64();
							MySQL* db = (handle >= 1 && handle <= self->mysql_handles.size())
								? self->mysql_handles[(size_t)handle - 1] : 0;
							if(op == "query" && db)
							{
								response["result"] = db->query(request["query"].to_string());
								response["insert_id"] = (f64)db->insert_id;
								response["affected"] = (f64)db->affected_rows;
								response["error_code"] = (f64)db->_preload_next_error_code;
								response["statement_info"] = db->error();
							}
							else if(op == "disconnect" && db)
							{
								if(db->request_leases > 0)
									db->request_leases--;
								self->mysql_handles[(size_t)handle - 1] = 0;
							}
						}
					}
					out = ucb_encode(response);
					if(buf == 0)
						self->hostcall_stage(stage_key, out);
				}
				if(buf != 0 && cap >= out.size())
					self->hostcall_write(buf, out);
				results[0] = Val((int32_t)out.size());
				return(std::monostate());
			}));
#endif
		if(mod == "env" && name == "uce_host_file_unlink")
			return(add([self](Caller, Span<const Val> args, Span<Val>) -> Result<std::monostate, Trap> {
				String path, current;
				self->hostcall_read(args[0].i32(), args[1].i32(), path);
				self->hostcall_read(args[2].i32(), args[3].i32(), current);
				String resolved = self->resolve_guest_write(path, current);
				if(resolved != "")
					::unlink(resolved.c_str());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_socket_connect")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String host;
				self->hostcall_read(args[0].i32(), args[1].i32(), host);
				int fd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
				if(fd >= 0)
				{
					struct sockaddr_in addr = {0};
					addr.sin_family = AF_INET;
					addr.sin_port = htons((short)args[2].i32());
					addr.sin_addr.s_addr = inet_addr(host.c_str());
					if(::connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
					{
						::close(fd);
						fd = -1;
					}
					else if(fd == 0)
					{
						int moved = ::dup(fd);
						::close(fd);
						fd = moved;
					}
					if(fd > 0 && context)
						context->resources.sockets.push_back(fd);
				}
				results[0] = Val((int64_t)(fd > 0 ? fd : 0));
				caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_socket_close")
			return(add([](Caller, Span<const Val> args, Span<Val>) -> Result<std::monostate, Trap> {
				::socket_close((u64)args[0].i64());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_socket_write")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String data;
				self->hostcall_read(args[1].i32(), args[2].i32(), data);
				results[0] = Val(::socket_write((u64)args[0].i64(), data) ? (int32_t)1 : (int32_t)0);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_socket_read")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				u64 sockfd = (u64)args[0].i64();
				u32 max_length = (u32)args[1].i32();
				u32 timeout = (u32)args[2].i32();
				int32_t buf = args[3].i32();
				u32 cap = (u32)args[4].i32();
				String key = std::to_string(sockfd) + ":" + std::to_string(max_length) + ":" + std::to_string(timeout);
				String out;
				if(buf != 0 && self->staged_socket_read_key == key)
				{
					out = self->staged_socket_read_result;
					self->staged_socket_read_key = "";
					self->staged_socket_read_result = "";
				}
				else
				{
					out = ::socket_read(sockfd, max_length, timeout);
					if(buf == 0)
					{
						self->staged_socket_read_key = key;
						self->staged_socket_read_result = out;
					}
				}
				if(buf != 0 && cap >= out.size())
					self->hostcall_write(buf, out);
				caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
				results[0] = Val((int32_t)out.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_server_start_http")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String key, bind, file, function, current;
				self->hostcall_read(args[0].i32(), args[1].i32(), key);
				self->hostcall_read(args[2].i32(), args[3].i32(), bind);
				self->hostcall_read(args[4].i32(), args[5].i32(), file);
				self->hostcall_read(args[6].i32(), args[7].i32(), function);
				self->hostcall_read(args[8].i32(), args[9].i32(), current);
				String resolved = self->resolve_guest_file(file, current);
				pid_t pid = 0;
				try
				{
					if(resolved != "")
						pid = ::server_start_http(key, bind, resolved, function);
				}
				catch(const std::exception& e)
				{
					fprintf(stderr, "[wasm server] start failed for key '%s': %s\n", key.c_str(), e.what());
				}
				catch(...)
				{
					fprintf(stderr, "[wasm server] start failed for key '%s'\n", key.c_str());
				}
				results[0] = Val((int32_t)pid);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_server_stop")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String key;
				self->hostcall_read(args[0].i32(), args[1].i32(), key);
				results[0] = Val(::server_stop(key) ? (int32_t)1 : (int32_t)0);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_task_spawn")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String key;
				self->hostcall_read(args[0].i32(), args[1].i32(), key);
				u64 callback_id = (u64)args[2].i64();
				f64 interval = args[3].f64();
				u64 timeout = (u64)args[4].i64();
				bool repeat = args[5].i32() != 0;
				// task()/task_repeat() fork and invoke this lambda only in the child
				// before the hostcall stack unwinds, so `self` points to the child's
				// copy of this per-request workspace. The parent request can return and
				// destroy its workspace without invalidating the child copy.
				auto run_callback = [self, callback_id]() {
					String error = self->run_task_callback(callback_id);
					if(error != "")
						fprintf(stderr, "[wasm task] callback failed: %s\n", error.c_str());
				};
				pid_t pid = 0;
				try
				{
					if(!repeat || (interval > 0 && std::isfinite(interval)))
						pid = repeat
							? ::task_repeat(key, interval, run_callback, timeout)
							: ::task(key, run_callback, timeout);
				}
				catch(const std::exception& e)
				{
					fprintf(stderr, "[wasm task] spawn failed for key '%s': %s\n", key.c_str(), e.what());
				}
				catch(...)
				{
					fprintf(stderr, "[wasm task] spawn failed for key '%s'\n", key.c_str());
				}
				results[0] = Val((int32_t)pid);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_task_pid")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String key;
				self->hostcall_read(args[0].i32(), args[1].i32(), key);
				results[0] = Val((int32_t)::task_pid(key));
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_task_kill")
			return(add([](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				results[0] = Val((int32_t)::task_kill((pid_t)args[0].i32(), args[1].i32()));
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_sleep_us")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				u64 usec = (u64)args[0].i64();
				while(usec >= 1000000ull)
				{
					unsigned int remaining = ::sleep((unsigned int)(usec / 1000000ull));
					if(remaining != 0)
					{
						caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
						results[0] = Val((int32_t)remaining);
						return(std::monostate());
					}
					usec %= 1000000ull;
				}
				if(usec > 0)
					::usleep((useconds_t)usec);
				caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
				results[0] = Val((int32_t)0);
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_regex")
			return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				// {op,pattern,subject,flags,replacement} in (UCEB2) → result out.
				// PCRE2 lives host-side; this runs the native regex_*.
				String encoded;
				self->hostcall_read(args[0].i32(), args[1].i32(), encoded);
				DValue request;
				String decode_error;
				DValue response;
				if(ucb_decode(encoded, request, &decode_error))
				{
					String op = request["op"].to_string();
					String pattern = request["pattern"].to_string();
					String subject = request["subject"].to_string();
					String flags = request["flags"].to_string();
					if(op == "match")
						response["bool"].set_bool(regex_match(pattern, subject, flags));
					else if(op == "search")
						response["tree"] = regex_search(pattern, subject, flags);
					else if(op == "search_all")
						response["tree"] = regex_search_all(pattern, subject, flags);
					else if(op == "replace")
						response["text"] = regex_replace(pattern, request["replacement"].to_string(), subject, flags);
					else if(op == "split")
						for(auto& part : regex_split(pattern, subject, flags))
						{
							DValue value;
							value = part;
							response["list"].push(value);
						}
				}
				String out = ucb_encode(response);
				u32 cap = (u32)args[3].i32();
				int32_t buf = args[2].i32();
				if(buf != 0 && cap >= out.size())
					self->hostcall_write(buf, out);
				results[0] = Val((int32_t)out.size());
				return(std::monostate());
			}));
		if(mod == "env" && name == "uce_host_component_resolve")
			return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
				String target, handler, current, resolved;
				self->hostcall_read(args[0].i32(), args[1].i32(), target);
				self->hostcall_read(args[2].i32(), args[3].i32(), handler);
				self->hostcall_read(args[4].i32(), args[5].i32(), current);
				int32_t slot = self->component_resolve(target, handler, current, resolved);
				u32 cap = (u32)args[7].i32();
				if(cap > 0)
				{
					if(resolved.size() >= cap)
						resolved = resolved.substr(0, cap - 1);
					resolved.push_back('\0');
					self->hostcall_write(args[6].i32(), resolved);
				}
				results[0] = Val(slot);
				caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
				return(std::monostate());
			}));

		// anything else (wasi-libc residue): a named trap — tolerated as long
		// as it is never called
		String label = mod + "." + name;
		return(add([label](Caller, Span<const Val>, Span<Val>) -> Result<std::monostate, Trap> {
			return(wasmtime::Trap("unimplemented host import called: " + std::string(label)));
		}));
	}

	String hostcall_read(int32_t ptr, int32_t len, String& out)
	{
		return(guest_read((u32)ptr, (u32)len, out));
	}

	void hostcall_write(int32_t ptr, const String& data)
	{
		guest_write((u32)ptr, data);
	}
};

// ---- public entry: one request through one workspace -----------------------

inline WasmResponse wasm_worker_serve(WasmWorker& worker, const DValue& context_tree, const String& entry_source_path,
	const String& handler = "render", const Request* request = 0)
{
	WasmResponse response;
	WasmWorkspace workspace(worker);
	if(request)
		workspace.set_perf_snapshot(my_pid, (u64)parent_pid, request->server ? request->server->request_count : 0, request->stats.time_init, request->stats.time_start);
	auto birth_start = std::chrono::steady_clock::now();
	String error = workspace.birth();
	workspace.workspace_birth_us = (u64)std::chrono::duration_cast<std::chrono::microseconds>(
		std::chrono::steady_clock::now() - birth_start).count();
	if(error == "")
		error = workspace.apply_context(context_tree);
	if(error == "")
		error = workspace.invoke_entry(entry_source_path, handler, &response.handler_present);
	if(error == "")
		error = workspace.collect(response);
	response.workspace_birth_us = workspace.workspace_birth_us;
	response.component_resolve_count = workspace.component_resolve_count;
	response.component_resolve_total_us = workspace.component_resolve_total_us;
	if(error != "")
	{
		response.ok = false;
		response.error = error;
		return(response);
	}
	response.ok = true;
	return(response);
}
