|

Migrate to Reef 0.7.6: Result/Option, TOML inline arrays, socket module rename

Migrate to Reef 0.7.6: Result/Option, TOML inline arrays, socket module rename

Reef 0.7.6 brought three breaking changes; adapt zyginit + zygctl:

- Result/Option stdlib migration: io.file/io.dir/net.unix/hashmap.get/
  toml_parse now return Result[T,Error] / Option[T]. Guard with
  res.is_err/unwrap_ok/unwrap_or and opt.is_some/unwrap. io.dir.list_dir
  also changed shape (returns the array; length via arr.length()).
  Struct-field generic-return inference needs explicit
  'let x: opt.Option[int] = ...' annotations (0.7.6 compiler bug).
- TOML inline-array flattening: 'key = [...]' now flattens to key.0 /
  key.#len instead of a raw string. New read_toml_array helper in
  config.reef; removes the now-dead parse_toml_array. Fixes silently
  empty requires/after/conflicts/environment/contract arrays.
- Rename local module 'socket' -> 'ctlsocket' (socket.reef ->
  ctlsocket.reef): stdlib net.unix now imports net.socket, clashing
  with the local short name.

Linux dev build green; integration 91/91, migrate_layout 5/5.
Author: Chris Tusa <chris.tusa@leafscale.com>
Date: Jul 17, 2026 19:47
Changeset: d07e2e820232b75e49368cd735af7fc2a7d91b8f
Branch: default

Diff

diff -r f716017622c4 -r d07e2e820232 CLAUDE.md
--- a/CLAUDE.md	Sat Jun 13 18:55:07 2026 -0500
+++ b/CLAUDE.md	Fri Jul 17 19:47:52 2026 -0500
@@ -216,7 +216,7 @@
 │   ├── depgraph.reef      # Dependency graph and topological sort
 │   ├── contract.reef      # Hammerhead process contract FFI bindings
 │   ├── supervisor.reef    # Service lifecycle management
-│   └── socket.reef        # Unix domain socket server for zygctl
+│   └── ctlsocket.reef     # Unix domain socket server for zygctl (module `ctlsocket`; renamed from `socket` to avoid a name clash with stdlib `net.socket`, which `net.unix` now imports as of Reef 0.7.6)
 ├── services/
 │   └── examples/          # Example TOML service definitions
 ├── tests/
diff -r f716017622c4 -r d07e2e820232 src/config.reef
--- a/src/config.reef	Sat Jun 13 18:55:07 2026 -0500
+++ b/src/config.reef	Fri Jul 17 19:47:52 2026 -0500
@@ -21,6 +21,7 @@
 import io.file
 import io.dir
 import core.str
+import core.result as res
 
 export
     type ServiceDef
@@ -93,9 +94,6 @@
     fn parse_service(filepath: string, name: string, svc_dir: string, svc: ServiceDef): bool
     fn scan_enabled(config_dir: string, names: [string], max_names: int): int
     fn load_enabled_services(config_dir: string, services: [ServiceDef], max_services: int): int
-
-    // Inline array helper (exported for testing)
-    fn parse_toml_array(value: string, result: [string], max_items: int): int
 end export
 
 // FFI: helpers.c — resolve a symlink or path to its canonical absolute form.
@@ -468,46 +466,31 @@
     return STOP_METHOD_CONTRACT()
 end parse_stop_method
 
-// Parse a TOML inline array like ["a", "b", "c"] into a string array.
-// Returns the number of items parsed.
-// Adapted from Coral's parse_toml_array pattern.
-fn parse_toml_array(value: string, result: [string], max_items: int): int
-    let len = str.length(value)
-    if len < 2
-        return 0
-    end if
-
-    if value[0] != '['
+// Read an inline-array TOML field from the flattened key/value model.
+//
+// Reef 0.7.5+ flattens `key = ["a", "b"]` into indexed keys `key.0`, `key.1`,
+// ... plus a synthetic `key.#len` count marker — there is no longer a single
+// `key` entry holding the raw "[...]" text. This reads up to `max_items`
+// elements of `prefix` into `result` and returns the number stored. A missing
+// field or empty array yields 0.
+fn read_toml_array(keys: [string], vals: [string], count: int, prefix: string, result: [string], max_items: int): int
+    let len_key = str.concat(prefix, ".#len")
+    if not toml.toml_has_key(keys, vals, count, len_key)
         return 0
     end if
-
-    mut count = 0
-    mut i = 1
-    mut in_string = false
-    mut item_start = 0
-
-    while i < len and count < max_items
-        let c = value[i]
-
-        if c == '"' and not in_string
-            in_string = true
-            item_start = i + 1
-        elif c == '"' and in_string
-            let item_len = i - item_start
-            if item_len > 0
-                result[count] = str.substring(value, item_start, item_len)
-                count = count + 1
-            end if
-            in_string = false
-        elif c == ']' and not in_string
-            return count
+    let n = toml.toml_get_int(keys, vals, count, len_key)
+    mut stored = 0
+    mut i = 0
+    while i < n and stored < max_items
+        let item_key = str.concat(str.concat(prefix, "."), str.from_int(i))
+        if toml.toml_has_key(keys, vals, count, item_key)
+            result[stored] = toml.toml_get(keys, vals, count, item_key)
+            stored = stored + 1
         end if
-
         i = i + 1
     end while
-
-    return count
-end parse_toml_array
+    return stored
+end read_toml_array
 
 // ============================================================================
 // Service parsing
@@ -549,14 +532,22 @@
         return false
     end if
 
-    let content = file.readFile(filepath)
+    let content_r = file.readFile(filepath)
+    if res.is_err(content_r)
+        return false
+    end if
+    let content = res.unwrap_ok(content_r)
     if str.length(content) == 0
         return false
     end if
 
     let keys = toml.toml_alloc_keys()
     let vals = toml.toml_alloc_values()
-    let count = toml.toml_parse(content, keys, vals)
+    let count_r = toml.toml_parse(content, keys, vals)
+    if res.is_err(count_r)
+        return false
+    end if
+    let count = res.unwrap_ok(count_r)
 
     if count == 0
         return false
@@ -590,10 +581,7 @@
         svc.working_dir = toml.toml_get(keys, vals, count, "exec.working_directory")
     end if
 
-    if toml.toml_has_key(keys, vals, count, "exec.environment")
-        let env_str = toml.toml_get(keys, vals, count, "exec.environment")
-        svc.environment_count = parse_toml_array(env_str, svc.environment, 16)
-    end if
+    svc.environment_count = read_toml_array(keys, vals, count, "exec.environment", svc.environment, 16)
 
     if toml.toml_has_key(keys, vals, count, "exec.user")
         svc.user = toml.toml_get(keys, vals, count, "exec.user")
@@ -617,31 +605,13 @@
     end if
 
     // [dependencies] section — inline arrays
-    if toml.toml_has_key(keys, vals, count, "dependencies.requires")
-        let req_str = toml.toml_get(keys, vals, count, "dependencies.requires")
-        svc.requires_count = parse_toml_array(req_str, svc.requires, 16)
-    end if
-
-    if toml.toml_has_key(keys, vals, count, "dependencies.after")
-        let after_str = toml.toml_get(keys, vals, count, "dependencies.after")
-        svc.after_count = parse_toml_array(after_str, svc.after, 16)
-    end if
-
-    if toml.toml_has_key(keys, vals, count, "dependencies.conflicts")
-        let conf_str = toml.toml_get(keys, vals, count, "dependencies.conflicts")
-        svc.conflicts_count = parse_toml_array(conf_str, svc.conflicts, 16)
-    end if
+    svc.requires_count = read_toml_array(keys, vals, count, "dependencies.requires", svc.requires, 16)
+    svc.after_count = read_toml_array(keys, vals, count, "dependencies.after", svc.after, 16)
+    svc.conflicts_count = read_toml_array(keys, vals, count, "dependencies.conflicts", svc.conflicts, 16)
 
     // [contract] section — inline arrays
-    if toml.toml_has_key(keys, vals, count, "contract.param")
-        let param_str = toml.toml_get(keys, vals, count, "contract.param")
-        svc.contract_param_count = parse_toml_array(param_str, svc.contract_param, 8)
-    end if
-
-    if toml.toml_has_key(keys, vals, count, "contract.fatal")
-        let fatal_str = toml.toml_get(keys, vals, count, "contract.fatal")
-        svc.contract_fatal_count = parse_toml_array(fatal_str, svc.contract_fatal, 8)
-    end if
+    svc.contract_param_count = read_toml_array(keys, vals, count, "contract.param", svc.contract_param, 8)
+    svc.contract_fatal_count = read_toml_array(keys, vals, count, "contract.fatal", svc.contract_fatal, 8)
 
     // [restart] section — all optional with defaults
     if toml.toml_has_key(keys, vals, count, "restart.on")
@@ -667,10 +637,7 @@
         svc.cond_exists_file = toml.toml_get(keys, vals, count, "condition.exists_file")
     end if
 
-    if toml.toml_has_key(keys, vals, count, "condition.exists_file_any")
-        let any_str = toml.toml_get(keys, vals, count, "condition.exists_file_any")
-        svc.cond_exists_file_any_count = parse_toml_array(any_str, svc.cond_exists_file_any, 16)
-    end if
+    svc.cond_exists_file_any_count = read_toml_array(keys, vals, count, "condition.exists_file_any", svc.cond_exists_file_any, 16)
 
     if toml.toml_has_key(keys, vals, count, "condition.command")
         svc.cond_command = toml.toml_get(keys, vals, count, "condition.command")
@@ -706,8 +673,12 @@
         return 0
     end if
 
-    let entries = new [string](128)
-    let entry_count = dir.list_dir(enabled_dir, entries, 128)
+    let entries_r = dir.list_dir(enabled_dir)
+    if res.is_err(entries_r)
+        return 0
+    end if
+    let entries = res.unwrap_ok(entries_r)
+    let entry_count = entries.length()
 
     mut name_count = 0
     mut i = 0
diff -r f716017622c4 -r d07e2e820232 src/ctlsocket.reef
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ctlsocket.reef	Fri Jul 17 19:47:52 2026 -0500
@@ -0,0 +1,510 @@
+/******************************************************************************
+                __               ____                __
+               / /   ___  ____ _/ __/_____________ _/ /__
+              / /   / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \
+             / /___/  __/ /_/ / __(__  ) /__/ /_/ / /  __/
+            /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/
+
+    (C)opyright 2025-2026, Leafscale, LLC -  https://www.leafscale.com
+
+    Project: zyginit
+   Filename: ctlsocket.reef
+    Authors: Chris Tusa <chris.tusa@leafscale.com>
+    License: <see LICENSE file included with this source code>
+Description: Unix domain socket server for zygctl communication
+
+******************************************************************************/
+
+// Protocol: newline-terminated text commands and responses.
+//   Request:  "COMMAND [ARG]\n"
+//   Response: plain text lines, connection closed after response.
+
+module ctlsocket
+
+import net.unix as unix
+import io.file
+import io.dir
+import sys.env as env
+import supervisor
+import config
+import shutdown
+import core.str
+import core.result as res
+import version
+import ui
+import ui_render
+
+export
+    fn create_socket(path: string): int
+    proc destroy_socket(path: string, server_fd: int)
+    proc handle_client(server_fd: int, table: supervisor.ServiceTable)
+    fn reload_requested(): bool
+    proc clear_reload_flag()
+    fn replace_requested(): bool
+    proc clear_replace_flag()
+    fn replace_wait_seconds(): int
+    fn shutdown_requested(): int
+    proc clear_shutdown_request()
+    fn runlevel_requested(): int
+    proc clear_runlevel_request()
+end export
+
+extern "C" fn zyginit_symlink(target: string, linkpath: string): int
+extern "C" fn zyginit_fsync_path(path: string): int
+
+// Reload flag — set by "reload" command, checked by main event loop
+mut g_reload_requested: bool = false
+
+fn reload_requested(): bool
+    return g_reload_requested
+end reload_requested
+
+proc clear_reload_flag()
+    g_reload_requested = false
+end clear_reload_flag
+
+// Replace flag — set by "replace" command, checked by main event loop.
+// Wait seconds is the optional --wait=N value; 0 means "refuse immediately
+// on transient state".
+mut g_replace_requested: bool = false
+mut g_replace_wait: int = 0
+
+fn replace_requested(): bool
+    return g_replace_requested
+end replace_requested
+
+proc clear_replace_flag()
+    g_replace_requested = false
+    g_replace_wait = 0
+end clear_replace_flag
+
+fn replace_wait_seconds(): int
+    return g_replace_wait
+end replace_wait_seconds
+
+// Shutdown request flag — set by halt/reboot/poweroff commands,
+// checked by main event loop each iteration.
+// 0 = no shutdown requested; non-zero = SHUT_HALT/REBOOT/POWEROFF value.
+mut g_shutdown_request: int = 0
+
+fn shutdown_requested(): int
+    return g_shutdown_request
+end shutdown_requested
+
+proc clear_shutdown_request()
+    g_shutdown_request = 0
+end clear_shutdown_request
+
+// Runlevel transition request — set by `single` or `multi` socket
+// commands, picked up by main loop and applied via runlevel_transition.
+// Sentinel values: -1 = no request; 0 = MULTI; 1 = SINGLE (matching
+// main.reef's RUNLEVEL_* constants).
+mut g_runlevel_request: int = 0 - 1
+
+fn runlevel_requested(): int
+    return g_runlevel_request
+end runlevel_requested
+
+proc clear_runlevel_request()
+    g_runlevel_request = 0 - 1
+end clear_runlevel_request
+
+// ============================================================================
+// Socket lifecycle
+// ============================================================================
+
+// Create and bind a Unix domain socket for listening.
+// Returns the server fd on success, -1 on error.
+fn create_socket(path: string): int
+    // Remove stale socket file if it exists. unlink failure is non-fatal —
+    // the subsequent listen will fail loudly if the path is unusable.
+    let unlink_rc = unix.unix_unlink(path)
+    if unlink_rc < 0
+        println("socket: unlink failed (non-fatal): " + path)
+    end if
+
+    let listen_r = unix.unix_listen(path, 5)
+    if res.is_err(listen_r)
+        println("socket: failed to listen on " + path)
+        return 0 - 1
+    end if
+    let fd = res.unwrap_ok(listen_r)
+
+    println("socket: listening on " + path)
+    return fd
+end create_socket
+
+// Close the socket and remove the socket file. Called during shutdown.
+// We fsync the parent directory after unlink to force the ZFS txg
+// commit before uadmin reboots — otherwise the next boot sees a stale
+// socket file in /var/run and create_socket fails (unlink runs again
+// but bind hits residual state). Without this, zygctl can't connect
+// after a zygctl-triggered reboot.
+proc destroy_socket(path: string, server_fd: int)
+    println("socket: closing server fd")
+    unix.unix_close(server_fd)
+    let rc = unix.unix_unlink(path)
+    if rc < 0
+        println("socket: unlink failed: " + path)
+    else
+        println("socket: unlinked " + path)
+    end if
+    let _ = zyginit_fsync_path(path)
+end destroy_socket
+
+// ============================================================================
+// Client handling
+// ============================================================================
+
+// Accept a client connection, read command, dispatch, respond, close.
+proc handle_client(server_fd: int, table: supervisor.ServiceTable)
+    let accept_r = unix.unix_accept(server_fd)
+    if res.is_err(accept_r)
+        return
+    end if
+    let client_fd = res.unwrap_ok(accept_r)
+
+    // Read the command (max 1024 bytes)
+    let recv_r = unix.unix_recv(client_fd, 1024)
+    if res.is_err(recv_r)
+        unix.unix_close(client_fd)
+        return
+    end if
+    let input = res.unwrap_ok(recv_r)
+    if str.length(input) == 0
+        unix.unix_close(client_fd)
+        return
+    end if
+
+    // Strip trailing newline/whitespace
+    let cmd_raw = str.trim_ws(input)
+
+    // Parse command and argument
+    let space_pos = str.index_of(cmd_raw, " ")
+    mut command = cmd_raw
+    mut arg = ""
+
+    if space_pos >= 0
+        command = str.substring(cmd_raw, 0, space_pos)
+        arg = str.substring(cmd_raw, space_pos + 1, str.length(cmd_raw) - space_pos - 1)
+    end if
+
+    // Dispatch
+    let response = dispatch_command(command, arg, table)
+
+    // Send response and close
+    unix.unix_send(client_fd, response)
+    unix.unix_close(client_fd)
+end handle_client
+
+// ============================================================================
+// Command dispatch
+// ============================================================================
+
+fn dispatch_command(command: string, arg: string, table: supervisor.ServiceTable): string
+    if command == "status"
+        // Parse optional PLAIN modifier: "PLAIN" or "PLAIN <name>"
+        mut plain = 0
+        mut svc_arg = arg
+        if str.length(arg) >= 5 and str.substring(arg, 0, 5) == "plain"
+            if str.length(arg) == 5
+                plain = 1
+                svc_arg = ""
+            elif str.substring(arg, 5, 1) == " "
+                plain = 1
+                svc_arg = str.substring(arg, 6, str.length(arg) - 6)
+            end if
+        end if
+        return cmd_status(table, svc_arg, plain)
+    elif command == "start"
+        return cmd_start(table, arg)
+    elif command == "stop"
+        return cmd_stop(table, arg)
+    elif command == "restart"
+        return cmd_restart(table, arg)
+    elif command == "log"
+        return cmd_log(table, arg)
+    elif command == "reload"
+        g_reload_requested = true
+        return "reload initiated\n"
+    elif command == "replace"
+        // Optional --wait=N argument
+        mut wait_seconds = 0
+        if str.length(arg) > 0
+            // Parse --wait=N
+            if str.substring(arg, 0, 7) == "--wait="
+                let val = str.substring(arg, 7, str.length(arg) - 7)
+                wait_seconds = parse_replace_int_or(val, 0)
+            else
+                return "error: replace: unknown argument: " + arg + "\n"
+            end if
+        end if
+        g_replace_requested = true
+        g_replace_wait = wait_seconds
+        return str.concat("replace queued (wait=", str.concat(int_to_str(wait_seconds), ")\n"))
+    elif command == "halt"
+        g_shutdown_request = shutdown.SHUT_HALT()
+        return "halting\n"
+    elif command == "reboot"
+        g_shutdown_request = shutdown.SHUT_REBOOT()
+        return "rebooting\n"
+    elif command == "poweroff"
+        g_shutdown_request = shutdown.SHUT_POWEROFF()
+        return "powering off\n"
+    elif command == "single"
+        g_runlevel_request = 1   // RUNLEVEL_SINGLE
+        return "transitioning to single-user\n"
+    elif command == "multi"
+        g_runlevel_request = 0   // RUNLEVEL_MULTI
+        return "transitioning to multi-user\n"
+    elif command == "multiuser"
+        // Legacy alias for backward compatibility
+        g_runlevel_request = 0
+        return "transitioning to multi-user\n"
+    elif command == "enable"
+        return cmd_enable(arg)
+    elif command == "disable"
+        return cmd_disable(arg)
+    elif command == "list"
+        // Parse optional PLAIN modifier
+        mut list_plain = 0
+        if arg == "plain"
+            list_plain = 1
+        end if
+        return cmd_list(table, list_plain)
+    elif command == "version"
+        return "zyginit " + version.VERSION() + "\n"
+    elif command == "help"
+        return cmd_help()
+    end if
+
+    return "error: unknown command: " + command + "\n"
+end dispatch_command
+
+// ============================================================================
+// Command implementations
+// ============================================================================
+
+fn cmd_status(table: supervisor.ServiceTable, arg: string, plain: int): string
+    let count = supervisor.service_count(table)
+
+    if str.length(arg) > 0
+        // Status of a single service — honor the plain flag from the caller
+        let idx = supervisor.find_service(table, arg)
+        if idx < 0
+            return "error: unknown service: " + arg + "\n"
+        end if
+        return ui_render.ui_render_status_one(table, idx, plain)
+    end if
+
+    // Status of all services
+    if count == 0
+        return "no services loaded\n"
+    end if
+
+    // plain=1: no banner, no escapes; plain=0: banner + colors via daemon's g_mode
+    return ui_render.ui_render_status(table, 1, plain)
+end cmd_status
+
+fn cmd_start(table: supervisor.ServiceTable, name: string): string
+    if str.length(name) == 0
+        return "error: usage: start <service>\n"
+    end if
+
+    let idx = supervisor.find_service(table, name)
+    if idx < 0
+        return "error: unknown service: " + name + "\n"
+    end if
+
+    let rt = supervisor.get_runtime(table, idx)
+    let state = supervisor.rt_state(rt)
+
+    if state == supervisor.STATE_RUNNING()
+        return name + " is already running\n"
+    end if
+
+    let conflict_peer = supervisor.conflicting_running_peer(table, idx)
+    if conflict_peer >= 0
+        let other = supervisor.service_name_at(table, conflict_peer)
+        return "error: cannot start " + name + ": conflicts with running service " + other + "\n"
+    end if
+
+    if supervisor.start_service(table, idx)
+        return name + " started\n"
+    end if
+
+    return "error: failed to start " + name + "\n"
+end cmd_start
+
+fn cmd_stop(table: supervisor.ServiceTable, name: string): string
+    if str.length(name) == 0
+        return "error: usage: stop <service>\n"
+    end if
+
+    let idx = supervisor.find_service(table, name)
+    if idx < 0
+        return "error: unknown service: " + name + "\n"
+    end if
+
+    if supervisor.stop_service(table, idx)
+        return name + " stopping\n"
+    end if
+
+    return name + " is not running\n"
+end cmd_stop
+
+fn cmd_restart(table: supervisor.ServiceTable, name: string): string
+    if str.length(name) == 0
+        return "error: usage: restart <service>\n"
+    end if
+
+    let idx = supervisor.find_service(table, name)
+    if idx < 0
+        return "error: unknown service: " + name + "\n"
+    end if
+
+    let rt = supervisor.get_runtime(table, idx)
+    let state = supervisor.rt_state(rt)
+
+    if state == supervisor.STATE_RUNNING()
+        supervisor.stop_service(table, idx)
+    end if
+
+    // Note: actual restart happens via the event loop when the stop completes
+    // and restart policy triggers. For immediate restart of a stopped service:
+    if state != supervisor.STATE_RUNNING()
+        if supervisor.start_service(table, idx)
+            return name + " restarted\n"
+        end if
+        return "error: failed to restart " + name + "\n"
+    end if
+
+    return name + " stop initiated (will restart via policy)\n"
+end cmd_restart
+
+fn cmd_list(table: supervisor.ServiceTable, plain: int): string
+    return ui_render.ui_render_list(table, plain)
+end cmd_list
+
+fn cmd_log(table: supervisor.ServiceTable, name: string): string
+    if str.length(name) == 0
+        return "error: usage: log <service>\n"
+    end if
+
+    let idx = supervisor.find_service(table, name)
+    if idx < 0
+        return "error: unknown service: " + name + "\n"
+    end if
+
+    return supervisor.get_service_log(name)
+end cmd_log
+
+fn cmd_enable(name: string): string
+    if str.length(name) == 0
+        return "error: usage: enable <service>\n"
+    end if
+
+    let config_dir = env.get_env_or("ZYGINIT_CONFIG_DIR", "/etc/zyginit")
+    let toml_path = str.concat(str.concat(config_dir, "/"), str.concat(name, ".toml"))
+
+    if not file.fileExists(toml_path)
+        return "error: no service definition found: " + toml_path + "\n"
+    end if
+
+    let enabled_dir = str.concat(config_dir, "/enabled.d")
+
+    if not dir.dir_exists(enabled_dir)
+        if res.is_err(dir.create_dir(enabled_dir))
+            return "error: cannot create " + enabled_dir + "\n"
+        end if
+    end if
+
+    let link_path = str.concat(str.concat(enabled_dir, "/"), name)
+
+    if file.fileExists(link_path)
+        return name + " is already enabled\n"
+    end if
+
+    let target = str.concat("../", str.concat(name, ".toml"))
+    let rc = zyginit_symlink(target, link_path)
+    if rc < 0
+        return "error: failed to create symlink for " + name + "\n"
+    end if
+
+    return name + " enabled\n"
+end cmd_enable
+
+fn cmd_disable(name: string): string
+    if str.length(name) == 0
+        return "error: usage: disable <service>\n"
+    end if
+
+    let config_dir = env.get_env_or("ZYGINIT_CONFIG_DIR", "/etc/zyginit")
+    let enabled_dir = str.concat(config_dir, "/enabled.d")
+    let link_path = str.concat(str.concat(enabled_dir, "/"), name)
+
+    if not file.fileExists(link_path)
+        return name + " is not enabled\n"
+    end if
+
+    if res.is_ok(file.deleteFile(link_path))
+        return name + " disabled\n"
+    end if
+
+    return "error: failed to remove " + link_path + "\n"
+end cmd_disable
+
+fn cmd_help(): string
+    return "commands: status [name], start <name>, stop <name>, restart <name>, enable <name>, disable <name>, reload, replace [--wait=N], halt, reboot, poweroff, single, multiuser, log <name>, list, version, help\n"
+end cmd_help
+
+// ============================================================================
+// Local helper functions
+// ============================================================================
+
+// Convert an integer to a string
+fn int_to_str(n: int): string
+    if n == 0
+        return "0"
+    end if
+
+    mut value = n
+    if n < 0
+        value = 0 - n
+    end if
+
+    mut result = ""
+    while value > 0
+        let digit = value % 10
+        result = str.concat(str.substring("0123456789", digit, 1), result)
+        value = value / 10
+    end while
+
+    if n < 0
+        result = str.concat("-", result)
+    end if
+
+    return result
+end int_to_str
+
+// Local int parser for replace --wait=N. Negative numbers and non-numeric
+// input both yield default_val. Empty string also yields default.
+fn parse_replace_int_or(s: string, default_val: int): int
+    let n = str.length(s)
+    if n == 0
+        return default_val
+    end if
+    mut result = 0
+    mut i = 0
+    while i < n
+        let c = str.char_at(s, i)
+        if c < '0' or c > '9'
+            return default_val
+        end if
+        result = result * 10 + (c - '0')
+        i = i + 1
+    end while
+    return result
+end parse_replace_int_or
+
+end module
diff -r f716017622c4 -r d07e2e820232 src/depgraph.reef
--- a/src/depgraph.reef	Sat Jun 13 18:55:07 2026 -0500
+++ b/src/depgraph.reef	Fri Jul 17 19:47:52 2026 -0500
@@ -20,6 +20,7 @@
 import config
 import collections.hashmap as hashmap
 import core.str
+import core.option as opt
 
 export
     type DepGraph
@@ -100,8 +101,9 @@
 // Add a service node to the graph. Returns its index, or -1 if full.
 fn add_service(graph: DepGraph, name: string): int
     // Check if already exists
-    if graph.name_map.has(name)
-        return graph.name_map.get(name)
+    let existing: opt.Option[int] = graph.name_map.get(name)
+    if opt.is_some(existing)
+        return opt.unwrap(existing)
     end if
 
     if graph.count >= MAX_SERVICES()
@@ -128,8 +130,10 @@
         return false
     end if
 
-    let from_idx = graph.name_map.get(name)
-    let to_idx = graph.name_map.get(dep_name)
+    let from_opt: opt.Option[int] = graph.name_map.get(name)
+    let to_opt: opt.Option[int] = graph.name_map.get(dep_name)
+    let from_idx = opt.unwrap(from_opt)
+    let to_idx = opt.unwrap(to_opt)
     let dc = graph.dep_count[from_idx]
 
     if dc >= MAX_DEPS()
@@ -274,7 +278,8 @@
         mut t = 0
         while t < tier_size
             let placed_name = tiers[tier_offset + t]
-            let placed_idx = graph.name_map.get(placed_name)
+            let placed_opt: opt.Option[int] = graph.name_map.get(placed_name)
+            let placed_idx = opt.unwrap(placed_opt)
 
             // Scan all nodes to find those that depend on placed_idx
             i = 0
diff -r f716017622c4 -r d07e2e820232 src/main.reef
--- a/src/main.reef	Sat Jun 13 18:55:07 2026 -0500
+++ b/src/main.reef	Fri Jul 17 19:47:52 2026 -0500
@@ -21,7 +21,7 @@
 import depgraph
 import contract
 import supervisor
-import socket
+import ctlsocket
 import shutdown
 import replace
 import ui
@@ -819,7 +819,7 @@
 // ============================================================================
 
 // Transition the system from current g_runlevel to target_level. Called
-// from the main event loop when socket.runlevel_requested() returns a
+// from the main event loop when ctlsocket.runlevel_requested() returns a
 // non-sentinel value. Stops services no longer in the active set
 // (reverse tier order) and starts services newly in the active set
 // (forward tier order). Updates g_runlevel and writes a RUN_LVL utmpx
@@ -1122,7 +1122,7 @@
     let bundle_fd = contract.open_bundle()
 
     // Unix domain socket for zygctl communication
-    let socket_fd = socket.create_socket(SOCKET_PATH())
+    let socket_fd = ctlsocket.create_socket(SOCKET_PATH())
 
     // ---- Phase 6: Start services ----
 
@@ -1182,25 +1182,25 @@
         end if
 
         // Check for reload requested via socket
-        if socket.reload_requested()
-            socket.clear_reload_flag()
+        if ctlsocket.reload_requested()
+            ctlsocket.clear_reload_flag()
             println("zyginit: reload requested via socket")
             reload_services(table)
         end if
 
         // Check for replace requested via socket
-        if socket.replace_requested()
-            let wait = socket.replace_wait_seconds()
-            socket.clear_replace_flag()
+        if ctlsocket.replace_requested()
+            let wait = ctlsocket.replace_wait_seconds()
+            ctlsocket.clear_replace_flag()
             replace_self(table, g_boot_time, wait)
             // If replace_self exec'd successfully, we never get here.
             // If it returned, log and continue running normally.
         end if
 
         // Check for shutdown requested via socket (halt/reboot/poweroff)
-        let req = socket.shutdown_requested()
+        let req = ctlsocket.shutdown_requested()
         if req != 0
-            socket.clear_shutdown_request()
+            ctlsocket.clear_shutdown_request()
             g_shutdown_type = req
             g_shutdown_reason = shutdown_type_to_reason(req)
             running = false
@@ -1208,9 +1208,9 @@
         end if
 
         // Check for runlevel transition requested via socket (single/multi)
-        let rl_req = socket.runlevel_requested()
+        let rl_req = ctlsocket.runlevel_requested()
         if rl_req >= 0
-            socket.clear_runlevel_request()
+            ctlsocket.clear_runlevel_request()
             transition_runlevel(table, tiers, tier_counts, num_tiers, rl_req)
         end if
 
@@ -1241,7 +1241,7 @@
     end if
 
     if socket_fd >= 0
-        socket.destroy_socket(SOCKET_PATH(), socket_fd)
+        ctlsocket.destroy_socket(SOCKET_PATH(), socket_fd)
     end if
 
     println("zyginit: shutdown complete")
@@ -1353,7 +1353,7 @@
     end if
 
     if ready > 0 and sock_idx >= 0 and poll.poll_readable(sock_idx)
-        socket.handle_client(socket_fd, table)
+        ctlsocket.handle_client(socket_fd, table)
     end if
 
     supervisor.check_stop_timeouts(table)
diff -r f716017622c4 -r d07e2e820232 src/replace.reef
--- a/src/replace.reef	Sat Jun 13 18:55:07 2026 -0500
+++ b/src/replace.reef	Fri Jul 17 19:47:52 2026 -0500
@@ -33,6 +33,7 @@
 import sys.fd as fd
 import sys.env as sysenv
 import core.str
+import core.result as res
 import encoding.toml
 import time.time as time
 
@@ -305,7 +306,12 @@
         return new_recovered_state()
     end if
 
-    let content = file.readFile(path)
+    let content_r = file.readFile(path)
+    if res.is_err(content_r)
+        let _ = zyginit_unlink(path)
+        return new_recovered_state()
+    end if
+    let content = res.unwrap_ok(content_r)
     if str.length(content) == 0
         let _ = zyginit_unlink(path)
         return new_recovered_state()
@@ -313,7 +319,13 @@
 
     let keys = toml.toml_alloc_keys()
     let vals = toml.toml_alloc_values()
-    let count = toml.toml_parse(content, keys, vals)
+    let count_r = toml.toml_parse(content, keys, vals)
+    if res.is_err(count_r)
+        println("replace: recover: parse error in " + path + " — ignoring")
+        let _ = zyginit_unlink(path)
+        return new_recovered_state()
+    end if
+    let count = res.unwrap_ok(count_r)
 
     if count == 0
         println("replace: recover: parse error or empty in " + path + " — ignoring")
diff -r f716017622c4 -r d07e2e820232 src/socket.reef
--- a/src/socket.reef	Sat Jun 13 18:55:07 2026 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,502 +0,0 @@
-/******************************************************************************
-                __               ____                __
-               / /   ___  ____ _/ __/_____________ _/ /__
-              / /   / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \
-             / /___/  __/ /_/ / __(__  ) /__/ /_/ / /  __/
-            /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/
-
-    (C)opyright 2025-2026, Leafscale, LLC -  https://www.leafscale.com
-
-    Project: zyginit
-   Filename: socket.reef
-    Authors: Chris Tusa <chris.tusa@leafscale.com>
-    License: <see LICENSE file included with this source code>
-Description: Unix domain socket server for zygctl communication
-
-******************************************************************************/
-
-// Protocol: newline-terminated text commands and responses.
-//   Request:  "COMMAND [ARG]\n"
-//   Response: plain text lines, connection closed after response.
-
-module socket
-
-import net.unix as unix
-import io.file
-import io.dir
-import sys.env as env
-import supervisor
-import config
-import shutdown
-import core.str
-import version
-import ui
-import ui_render
-
-export
-    fn create_socket(path: string): int
-    proc destroy_socket(path: string, server_fd: int)
-    proc handle_client(server_fd: int, table: supervisor.ServiceTable)
-    fn reload_requested(): bool
-    proc clear_reload_flag()
-    fn replace_requested(): bool
-    proc clear_replace_flag()
-    fn replace_wait_seconds(): int
-    fn shutdown_requested(): int
-    proc clear_shutdown_request()
-    fn runlevel_requested(): int
-    proc clear_runlevel_request()
-end export
-
-extern "C" fn zyginit_symlink(target: string, linkpath: string): int
-extern "C" fn zyginit_fsync_path(path: string): int
-
-// Reload flag — set by "reload" command, checked by main event loop
-mut g_reload_requested: bool = false
-
-fn reload_requested(): bool
-    return g_reload_requested
-end reload_requested
-
-proc clear_reload_flag()
-    g_reload_requested = false
-end clear_reload_flag
-
-// Replace flag — set by "replace" command, checked by main event loop.
-// Wait seconds is the optional --wait=N value; 0 means "refuse immediately
-// on transient state".
-mut g_replace_requested: bool = false
-mut g_replace_wait: int = 0
-
-fn replace_requested(): bool
-    return g_replace_requested
-end replace_requested
-
-proc clear_replace_flag()
-    g_replace_requested = false
-    g_replace_wait = 0
-end clear_replace_flag
-
-fn replace_wait_seconds(): int
-    return g_replace_wait
-end replace_wait_seconds
-
-// Shutdown request flag — set by halt/reboot/poweroff commands,
-// checked by main event loop each iteration.
-// 0 = no shutdown requested; non-zero = SHUT_HALT/REBOOT/POWEROFF value.
-mut g_shutdown_request: int = 0
-
-fn shutdown_requested(): int
-    return g_shutdown_request
-end shutdown_requested
-
-proc clear_shutdown_request()
-    g_shutdown_request = 0
-end clear_shutdown_request
-
-// Runlevel transition request — set by `single` or `multi` socket
-// commands, picked up by main loop and applied via runlevel_transition.
-// Sentinel values: -1 = no request; 0 = MULTI; 1 = SINGLE (matching
-// main.reef's RUNLEVEL_* constants).
-mut g_runlevel_request: int = 0 - 1
-
-fn runlevel_requested(): int
-    return g_runlevel_request
-end runlevel_requested
-
-proc clear_runlevel_request()
-    g_runlevel_request = 0 - 1
-end clear_runlevel_request
-
-// ============================================================================
-// Socket lifecycle
-// ============================================================================
-
-// Create and bind a Unix domain socket for listening.
-// Returns the server fd on success, -1 on error.
-fn create_socket(path: string): int
-    // Remove stale socket file if it exists. unlink failure is non-fatal —
-    // the subsequent listen will fail loudly if the path is unusable.
-    let unlink_rc = unix.unix_unlink(path)
-    if unlink_rc < 0
-        println("socket: unlink failed (non-fatal): " + path)
-    end if
-
-    let fd = unix.unix_listen(path, 5)
-    if fd < 0
-        println("socket: failed to listen on " + path)
-        return 0 - 1
-    end if
-
-    println("socket: listening on " + path)
-    return fd
-end create_socket
-
-// Close the socket and remove the socket file. Called during shutdown.
-// We fsync the parent directory after unlink to force the ZFS txg
-// commit before uadmin reboots — otherwise the next boot sees a stale
-// socket file in /var/run and create_socket fails (unlink runs again
-// but bind hits residual state). Without this, zygctl can't connect
-// after a zygctl-triggered reboot.
-proc destroy_socket(path: string, server_fd: int)
-    println("socket: closing server fd")
-    unix.unix_close(server_fd)
-    let rc = unix.unix_unlink(path)
-    if rc < 0
-        println("socket: unlink failed: " + path)
-    else
-        println("socket: unlinked " + path)
-    end if
-    let _ = zyginit_fsync_path(path)
-end destroy_socket
-
-// ============================================================================
-// Client handling
-// ============================================================================
-
-// Accept a client connection, read command, dispatch, respond, close.
-proc handle_client(server_fd: int, table: supervisor.ServiceTable)
-    let client_fd = unix.unix_accept(server_fd)
-    if client_fd < 0
-        return
-    end if
-
-    // Read the command (max 1024 bytes)
-    let input = unix.unix_recv(client_fd, 1024)
-    if str.length(input) == 0
-        unix.unix_close(client_fd)
-        return
-    end if
-
-    // Strip trailing newline/whitespace
-    let cmd_raw = str.trim_ws(input)
-
-    // Parse command and argument
-    let space_pos = str.index_of(cmd_raw, " ")
-    mut command = cmd_raw
-    mut arg = ""
-
-    if space_pos >= 0
-        command = str.substring(cmd_raw, 0, space_pos)
-        arg = str.substring(cmd_raw, space_pos + 1, str.length(cmd_raw) - space_pos - 1)
-    end if
-
-    // Dispatch
-    let response = dispatch_command(command, arg, table)
-
-    // Send response and close
-    unix.unix_send(client_fd, response)
-    unix.unix_close(client_fd)
-end handle_client
-
-// ============================================================================
-// Command dispatch
-// ============================================================================
-
-fn dispatch_command(command: string, arg: string, table: supervisor.ServiceTable): string
-    if command == "status"
-        // Parse optional PLAIN modifier: "PLAIN" or "PLAIN <name>"
-        mut plain = 0
-        mut svc_arg = arg
-        if str.length(arg) >= 5 and str.substring(arg, 0, 5) == "plain"
-            if str.length(arg) == 5
-                plain = 1
-                svc_arg = ""
-            elif str.substring(arg, 5, 1) == " "
-                plain = 1
-                svc_arg = str.substring(arg, 6, str.length(arg) - 6)
-            end if
-        end if
-        return cmd_status(table, svc_arg, plain)
-    elif command == "start"
-        return cmd_start(table, arg)
-    elif command == "stop"
-        return cmd_stop(table, arg)
-    elif command == "restart"
-        return cmd_restart(table, arg)
-    elif command == "log"
-        return cmd_log(table, arg)
-    elif command == "reload"
-        g_reload_requested = true
-        return "reload initiated\n"
-    elif command == "replace"
-        // Optional --wait=N argument
-        mut wait_seconds = 0
-        if str.length(arg) > 0
-            // Parse --wait=N
-            if str.substring(arg, 0, 7) == "--wait="
-                let val = str.substring(arg, 7, str.length(arg) - 7)
-                wait_seconds = parse_replace_int_or(val, 0)
-            else
-                return "error: replace: unknown argument: " + arg + "\n"
-            end if
-        end if
-        g_replace_requested = true
-        g_replace_wait = wait_seconds
-        return str.concat("replace queued (wait=", str.concat(int_to_str(wait_seconds), ")\n"))
-    elif command == "halt"
-        g_shutdown_request = shutdown.SHUT_HALT()
-        return "halting\n"
-    elif command == "reboot"
-        g_shutdown_request = shutdown.SHUT_REBOOT()
-        return "rebooting\n"
-    elif command == "poweroff"
-        g_shutdown_request = shutdown.SHUT_POWEROFF()
-        return "powering off\n"
-    elif command == "single"
-        g_runlevel_request = 1   // RUNLEVEL_SINGLE
-        return "transitioning to single-user\n"
-    elif command == "multi"
-        g_runlevel_request = 0   // RUNLEVEL_MULTI
-        return "transitioning to multi-user\n"
-    elif command == "multiuser"
-        // Legacy alias for backward compatibility
-        g_runlevel_request = 0
-        return "transitioning to multi-user\n"
-    elif command == "enable"
-        return cmd_enable(arg)
-    elif command == "disable"
-        return cmd_disable(arg)
-    elif command == "list"
-        // Parse optional PLAIN modifier
-        mut list_plain = 0
-        if arg == "plain"
-            list_plain = 1
-        end if
-        return cmd_list(table, list_plain)
-    elif command == "version"
-        return "zyginit " + version.VERSION() + "\n"
-    elif command == "help"
-        return cmd_help()
-    end if
-
-    return "error: unknown command: " + command + "\n"
-end dispatch_command
-
-// ============================================================================
-// Command implementations
-// ============================================================================
-
-fn cmd_status(table: supervisor.ServiceTable, arg: string, plain: int): string
-    let count = supervisor.service_count(table)
-
-    if str.length(arg) > 0
-        // Status of a single service — honor the plain flag from the caller
-        let idx = supervisor.find_service(table, arg)
-        if idx < 0
-            return "error: unknown service: " + arg + "\n"
-        end if
-        return ui_render.ui_render_status_one(table, idx, plain)
-    end if
-
-    // Status of all services
-    if count == 0
-        return "no services loaded\n"
-    end if
-
-    // plain=1: no banner, no escapes; plain=0: banner + colors via daemon's g_mode
-    return ui_render.ui_render_status(table, 1, plain)
-end cmd_status
-
-fn cmd_start(table: supervisor.ServiceTable, name: string): string
-    if str.length(name) == 0
-        return "error: usage: start <service>\n"
-    end if
-
-    let idx = supervisor.find_service(table, name)
-    if idx < 0
-        return "error: unknown service: " + name + "\n"
-    end if
-
-    let rt = supervisor.get_runtime(table, idx)
-    let state = supervisor.rt_state(rt)
-
-    if state == supervisor.STATE_RUNNING()
-        return name + " is already running\n"
-    end if
-
-    let conflict_peer = supervisor.conflicting_running_peer(table, idx)
-    if conflict_peer >= 0
-        let other = supervisor.service_name_at(table, conflict_peer)
-        return "error: cannot start " + name + ": conflicts with running service " + other + "\n"
-    end if
-
-    if supervisor.start_service(table, idx)
-        return name + " started\n"
-    end if
-
-    return "error: failed to start " + name + "\n"
-end cmd_start
-
-fn cmd_stop(table: supervisor.ServiceTable, name: string): string
-    if str.length(name) == 0
-        return "error: usage: stop <service>\n"
-    end if
-
-    let idx = supervisor.find_service(table, name)
-    if idx < 0
-        return "error: unknown service: " + name + "\n"
-    end if
-
-    if supervisor.stop_service(table, idx)
-        return name + " stopping\n"
-    end if
-
-    return name + " is not running\n"
-end cmd_stop
-
-fn cmd_restart(table: supervisor.ServiceTable, name: string): string
-    if str.length(name) == 0
-        return "error: usage: restart <service>\n"
-    end if
-
-    let idx = supervisor.find_service(table, name)
-    if idx < 0
-        return "error: unknown service: " + name + "\n"
-    end if
-
-    let rt = supervisor.get_runtime(table, idx)
-    let state = supervisor.rt_state(rt)
-
-    if state == supervisor.STATE_RUNNING()
-        supervisor.stop_service(table, idx)
-    end if
-
-    // Note: actual restart happens via the event loop when the stop completes
-    // and restart policy triggers. For immediate restart of a stopped service:
-    if state != supervisor.STATE_RUNNING()
-        if supervisor.start_service(table, idx)
-            return name + " restarted\n"
-        end if
-        return "error: failed to restart " + name + "\n"
-    end if
-
-    return name + " stop initiated (will restart via policy)\n"
-end cmd_restart
-
-fn cmd_list(table: supervisor.ServiceTable, plain: int): string
-    return ui_render.ui_render_list(table, plain)
-end cmd_list
-
-fn cmd_log(table: supervisor.ServiceTable, name: string): string
-    if str.length(name) == 0
-        return "error: usage: log <service>\n"
-    end if
-
-    let idx = supervisor.find_service(table, name)
-    if idx < 0
-        return "error: unknown service: " + name + "\n"
-    end if
-
-    return supervisor.get_service_log(name)
-end cmd_log
-
-fn cmd_enable(name: string): string
-    if str.length(name) == 0
-        return "error: usage: enable <service>\n"
-    end if
-
-    let config_dir = env.get_env_or("ZYGINIT_CONFIG_DIR", "/etc/zyginit")
-    let toml_path = str.concat(str.concat(config_dir, "/"), str.concat(name, ".toml"))
-
-    if not file.fileExists(toml_path)
-        return "error: no service definition found: " + toml_path + "\n"
-    end if
-
-    let enabled_dir = str.concat(config_dir, "/enabled.d")
-
-    if not dir.dir_exists(enabled_dir)
-        if not dir.create_dir(enabled_dir)
-            return "error: cannot create " + enabled_dir + "\n"
-        end if
-    end if
-
-    let link_path = str.concat(str.concat(enabled_dir, "/"), name)
-
-    if file.fileExists(link_path)
-        return name + " is already enabled\n"
-    end if
-
-    let target = str.concat("../", str.concat(name, ".toml"))
-    let rc = zyginit_symlink(target, link_path)
-    if rc < 0
-        return "error: failed to create symlink for " + name + "\n"
-    end if
-
-    return name + " enabled\n"
-end cmd_enable
-
-fn cmd_disable(name: string): string
-    if str.length(name) == 0
-        return "error: usage: disable <service>\n"
-    end if
-
-    let config_dir = env.get_env_or("ZYGINIT_CONFIG_DIR", "/etc/zyginit")
-    let enabled_dir = str.concat(config_dir, "/enabled.d")
-    let link_path = str.concat(str.concat(enabled_dir, "/"), name)
-
-    if not file.fileExists(link_path)
-        return name + " is not enabled\n"
-    end if
-
-    if file.deleteFile(link_path)
-        return name + " disabled\n"
-    end if
-
-    return "error: failed to remove " + link_path + "\n"
-end cmd_disable
-
-fn cmd_help(): string
-    return "commands: status [name], start <name>, stop <name>, restart <name>, enable <name>, disable <name>, reload, replace [--wait=N], halt, reboot, poweroff, single, multiuser, log <name>, list, version, help\n"
-end cmd_help
-
-// ============================================================================
-// Local helper functions
-// ============================================================================
-
-// Convert an integer to a string
-fn int_to_str(n: int): string
-    if n == 0
-        return "0"
-    end if
-
-    mut value = n
-    if n < 0
-        value = 0 - n
-    end if
-
-    mut result = ""
-    while value > 0
-        let digit = value % 10
-        result = str.concat(str.substring("0123456789", digit, 1), result)
-        value = value / 10
-    end while
-
-    if n < 0
-        result = str.concat("-", result)
-    end if
-
-    return result
-end int_to_str
-
-// Local int parser for replace --wait=N. Negative numbers and non-numeric
-// input both yield default_val. Empty string also yields default.
-fn parse_replace_int_or(s: string, default_val: int): int
-    let n = str.length(s)
-    if n == 0
-        return default_val
-    end if
-    mut result = 0
-    mut i = 0
-    while i < n
-        let c = str.char_at(s, i)
-        if c < '0' or c > '9'
-            return default_val
-        end if
-        result = result * 10 + (c - '0')
-        i = i + 1
-    end while
-    return result
-end parse_replace_int_or
-
-end module
diff -r f716017622c4 -r d07e2e820232 src/supervisor.reef
--- a/src/supervisor.reef	Sat Jun 13 18:55:07 2026 -0500
+++ b/src/supervisor.reef	Fri Jul 17 19:47:52 2026 -0500
@@ -31,6 +31,8 @@
 import time.clock as clock
 import collections.hashmap as hashmap
 import core.str
+import core.result as res
+import core.option as opt
 
 export
     type ServiceRuntime
@@ -279,16 +281,18 @@
 end add_service
 
 fn find_service(table: ServiceTable, name: string): int
-    if table.name_map.has(name)
-        return table.name_map.get(name)
+    let idx: opt.Option[int] = table.name_map.get(name)
+    if opt.is_some(idx)
+        return opt.unwrap(idx)
     end if
     return 0 - 1
 end find_service
 
 fn find_by_contract(table: ServiceTable, contract_id: int): int
     let key = int_to_str(contract_id)
-    if table.contract_map.has(key)
-        return table.contract_map.get(key)
+    let idx: opt.Option[int] = table.contract_map.get(key)
+    if opt.is_some(idx)
+        return opt.unwrap(idx)
     end if
     return 0 - 1
 end find_by_contract
@@ -1110,7 +1114,11 @@
     if not file.fileExists(log_path)
         return "no log available for " + name + "\n"
     end if
-    let content = file.readFile(log_path)
+    let content_r = file.readFile(log_path)
+    if res.is_err(content_r)
+        return "no log available for " + name + "\n"
+    end if
+    let content = res.unwrap_ok(content_r)
     if str.length(content) == 0
         return "(empty log)\n"
     end if
diff -r f716017622c4 -r d07e2e820232 tools/zygctl/src/main.reef
--- a/tools/zygctl/src/main.reef	Sat Jun 13 18:55:07 2026 -0500
+++ b/tools/zygctl/src/main.reef	Fri Jul 17 19:47:52 2026 -0500
@@ -24,6 +24,7 @@
 import io.file
 import io.dir
 import core.str
+import core.result as res
 import version
 
 extern "C" fn zyginit_symlink(target: string, linkpath: string): int
@@ -103,15 +104,17 @@
             req = str.concat(req, args.get(2))
         end if
         req = str.concat(req, "\n")
-        let fd = unix.unix_connect(SOCKET_PATH())
-        if fd < 0
+        let connect_r = unix.unix_connect(SOCKET_PATH())
+        if res.is_err(connect_r)
             println("zygctl: cannot connect to zyginit at " + SOCKET_PATH())
             println("zygctl: is zyginit running?")
             return
         end if
+        let fd = res.unwrap_ok(connect_r)
         unix.unix_send(fd, req)
-        let resp = unix.unix_recv(fd, 4096)
+        let resp_r = unix.unix_recv(fd, 4096)
         unix.unix_close(fd)
+        let resp = res.unwrap_or(resp_r, "")
         if str.length(resp) > 0
             print(resp)
         end if
@@ -124,15 +127,17 @@
             req = "list plain"
         end if
         req = str.concat(req, "\n")
-        let fd = unix.unix_connect(SOCKET_PATH())
-        if fd < 0
+        let connect_r = unix.unix_connect(SOCKET_PATH())
+        if res.is_err(connect_r)
             println("zygctl: cannot connect to zyginit at " + SOCKET_PATH())
             println("zygctl: is zyginit running?")
             return
         end if
+        let fd = res.unwrap_ok(connect_r)
         unix.unix_send(fd, req)
-        let resp = unix.unix_recv(fd, 4096)
+        let resp_r = unix.unix_recv(fd, 4096)
         unix.unix_close(fd)
+        let resp = res.unwrap_or(resp_r, "")
         if str.length(resp) > 0
             print(resp)
         end if
@@ -151,20 +156,22 @@
 
     // Connect to zyginit socket
     let sock_path = SOCKET_PATH()
-    let fd = unix.unix_connect(sock_path)
+    let connect_r = unix.unix_connect(sock_path)
 
-    if fd < 0
+    if res.is_err(connect_r)
         println("zygctl: cannot connect to zyginit at " + sock_path)
         println("zygctl: is zyginit running?")
         return
     end if
+    let fd = res.unwrap_ok(connect_r)
 
     // Send command
     unix.unix_send(fd, message)
 
     // Read response
-    let response = unix.unix_recv(fd, 4096)
+    let response_r = unix.unix_recv(fd, 4096)
     unix.unix_close(fd)
+    let response = res.unwrap_or(response_r, "")
 
     if str.length(response) > 0
         print(response)
@@ -188,7 +195,7 @@
 
     // Create enabled.d/ if it does not exist
     if not dir.dir_exists(enabled_dir)
-        if not dir.create_dir(enabled_dir)
+        if res.is_err(dir.create_dir(enabled_dir))
             println("error: cannot create " + enabled_dir)
             return
         end if
@@ -222,7 +229,7 @@
         return
     end if
 
-    if file.deleteFile(link_path)
+    if res.is_ok(file.deleteFile(link_path))
         println(name + " disabled")
     else
         println("error: failed to remove " + link_path)