|
root / docs / IMPLEMENTATION_PLAN.md
IMPLEMENTATION_PLAN.md markdown 438 lines 13.6 KB

zyginit Implementation Plan

Status: Complete — All 7 phases implemented, plus post-phase enhancements
Date: 2026-03-06
Author: Chris Tusa / Claude

Project Layout

Following Coral's conventions (source_dirs = ["src"], entry = "src/main.reef"):

zyginit/
├── reef.toml                # Reef manifest
├── SRCHEADER.txt            # Leafscale source header template
├── CLAUDE.md                # Project context
├── .hgignore                # Mercurial ignore
├── src/
│   ├── main.reef            # Entry point — event loop (Phase 5)
│   ├── config.reef          # TOML parser + ServiceDef types (Phase 1)
│   ├── depgraph.reef        # Dependency graph + topo sort (Phase 2)
│   ├── contract.reef        # Hammerhead process contract FFI (Phase 3)
│   ├── supervisor.reef      # Service lifecycle management (Phase 4)
│   └── socket.reef          # Unix domain socket server (Phase 6)
├── docs/
│   ├── DESIGN.md            # Architecture and design
│   └── IMPLEMENTATION_PLAN.md  # This file
├── services/
│   └── examples/            # Example TOML service definitions
├── tests/
│   └── (test TOML files, test programs)
├── stub/                    # Original Rust-like stubs (design reference only)
└── tools/
    └── zygctl/              # Separate CLI binary
        ├── reef.toml
        └── src/
            └── main.reef

Phase Dependency Graph

Phase 1: config.reef (TOML parsing, ServiceDef types)
    │
    ▼
Phase 2: depgraph.reef (dependency graph, topo sort)
    │
    ├──────────────────────┐
    ▼                      ▼
Phase 3: contract.reef   Phase 6: socket.reef
    │                      │
    ▼                      │
Phase 4: supervisor.reef ◄─┘
    │
    ▼
Phase 5: main.reef (event loop wiring)
    │
    ▼
Phase 7: tools/zygctl (separate binary)

Phase 1: config.reef — TOML Config Parser

Purpose: Parse /etc/zyginit/*.toml service definitions into ServiceDef structs.
Testable on Linux: Yes — fully portable.

Imports

import encoding.toml as toml
import io.file as file
import core.str

Types

type ServiceDef = struct
    name: string
    description: string
    service_type: int          // 1=daemon, 2=oneshot, 3=transient

    start_cmd: string
    stop_cmd: string           // empty if not specified

    stop_method: int           // 1=contract, 2=exec
    stop_signal: string        // "TERM", "HUP", etc.
    stop_timeout: int          // seconds, default 60

    requires: [string]         // hard dependencies
    requires_count: int
    after: [string]            // ordering dependencies
    after_count: int

    contract_param: [string]
    contract_param_count: int
    contract_fatal: [string]
    contract_fatal_count: int

    restart_policy: int        // 1=always, 2=failure, 3=never
    restart_delay: int         // seconds, default 5
    max_retries: int           // -1 = unlimited, default 3
end ServiceDef

Functions

  • new_service_def(): ServiceDef — create with defaults
  • parse_toml_array(value, result, max): int — parse ["a", "b"] inline arrays
  • parse_service_type(type_str): int — "daemon"→1, "oneshot"→2, "transient"→3
  • parse_restart_policy(policy_str): int — "always"→1, "failure"→2, "never"→3
  • parse_stop_method(method_str): int — "contract"→1, "exec"→2
  • parse_service(filepath, svc): bool — parse one TOML file into ServiceDef
  • scan_enabled(config_dir, paths, max): int — list enabled.d/ entries
  • load_enabled_services(config_dir, services, max): int — load all enabled services

Key Implementation Notes

  • Reef's TOML parser flattens sections: [service] + name = "sshd" → key "service.name"
  • Inline arrays like requires = ["a", "b"] need custom parsing (Coral has proven pattern)
  • No readlink needed: symlink name in enabled.d maps directly to <name>.toml

Phase 2: depgraph.reef — Dependency Graph + Topological Sort

Purpose: Build DAG from service dependencies, compute parallel boot tiers.
Testable on Linux: Yes — fully portable.

Imports

import collections.hashmap as hashmap
import core.str

Types

type DepGraph = struct
    names: [string]
    count: int
    name_map: hashmap.HashMap[int]  // name → index
    deps: [int]                     // flattened adjacency (MAX_SERVICES * MAX_DEPS)
    dep_count: [int]                // deps per node
    in_degree: [int]                // for topo sort
end DepGraph

Functions

  • new_depgraph(): DepGraph
  • add_service(graph, name): int — returns assigned index
  • add_dependency(graph, name, dep_name): bool
  • build_graph(graph, services, count): bool — populate from ServiceDef array
  • topo_sort(graph, tiers, tier_counts, max_tiers): int — Kahn's algorithm
  • has_cycle(graph): bool

Algorithm: Kahn's Topological Sort

  1. Find all nodes with in_degree == 0 → tier 0
  2. Remove those nodes, decrement in_degree of dependents
  3. Nodes reaching in_degree 0 form next tier
  4. Repeat until all placed
  5. Remaining nodes with in_degree > 0 → cycle error

Output: flat tiers[string] array + tier_counts[int] array. Services within a
tier can start in parallel.

Phase 3: contract.reef — Hammerhead Process Contract FFI

Purpose: Reef wrappers around libcontract for kernel-level service process tracking.
Testable on Linux: No — Hammerhead only. Compile-test only on Linux.

FFI Declarations

extern "C" fn ct_tmpl_activate(fd: int): int
extern "C" fn ct_tmpl_clear(fd: int): int
extern "C" fn ct_tmpl_set_informative(fd: int, events: int): int
extern "C" fn ct_tmpl_set_critical(fd: int, events: int): int
extern "C" fn ct_pr_tmpl_set_fatal(fd: int, events: int): int
extern "C" fn ct_pr_tmpl_set_param(fd: int, params: int): int
extern "C" fn ct_status_read(fd: int, detail: int, statp: pointer): int
extern "C" fn ct_status_free(statp: pointer)
extern "C" fn ct_status_get_id(statp: pointer): int
extern "C" fn ct_ctl_abandon(fd: int): int
extern "C" fn ct_ctl_adopt(fd: int): int
extern "C" fn sigsend(idtype: int, id: int, sig: int): int

Types

type Contract = struct
    id: int
    service_name: string
    bundle_fd: int
end Contract

Functions

  • setup_template(): int — open/configure/activate contract template
  • get_latest_contract(): int — read contract ID after fork
  • clear_template(tmpl_fd): int
  • open_bundle(): int — open bundle fd for poll()
  • kill_contract(contract_id, sig): int — signal all processes in contract
  • abandon_contract(contract_id): int
  • adopt_contract(contract_id): int — re-adopt after zyginit restart

Build Note

Requires -l contract linker flag on Hammerhead.

Risk: ct_status_read pointer semantics

ct_status_read takes pointer-to-opaque-pointer. Needs unsafe block and careful
FFI handling. Must prototype on Hammerhead VM.

Phase 4: supervisor.reef — Service Lifecycle Management

Purpose: Fork/exec services within contracts, manage state machine, restart policies.
Testable on Linux: Partial — fork/exec works, contracts do not.

Imports

import config
import contract
import sys.process as process
import sys.fd as fd
import sys.signal as signal
import collections.hashmap as hashmap
import core.str

Types

// State constants: DISABLED=0, WAITING=1, STARTING=2, RUNNING=3,
//                  STOPPING=4, STOPPED=5, FAILED=6, MAINTENANCE=7

type ServiceRuntime = struct
    def: config.ServiceDef
    state: int
    contract_id: int           // -1 if none
    pid: int                   // -1 if none
    restart_count: int
    last_exit_code: int        // -1 if not exited
    last_start_time: int
    stop_requested_time: int
end ServiceRuntime

type ServiceTable = struct
    runtimes: [ServiceRuntime]
    count: int
    name_map: hashmap.HashMap[int]      // name → index
    contract_map: hashmap.HashMap[int]  // contract_id_str → index
end ServiceTable

Functions

  • new_service_table(max): ServiceTable
  • add_service(table, def): int
  • find_service(table, name): int
  • find_by_contract(table, contract_id): int
  • start_service(table, idx): bool — fork, contract setup, exec
  • stop_service(table, idx): bool — signal or exec stop command
  • handle_contract_event(table, contract_id, exit_code) — apply restart policy
  • check_stop_timeouts(table) — escalate to SIGKILL if timeout exceeded
  • check_restart_delays(table) — restart services whose delay has elapsed
  • state_name(state): string — human-readable state
  • get_status_line(table, idx): string

start_service() Flow

  1. contract.setup_template() → template fd
  2. process.process_fork() → pid
  3. Child: process.process_setsid(), parse args, process.process_exec()
  4. Parent: contract.get_latest_contract() → contract_id
  5. contract.clear_template(tmpl_fd)
  6. Update runtime: state=RUNNING, pid, contract_id, timestamp

Restart Policy Logic (handle_contract_event)

  • RESTART_ALWAYS → schedule restart after delay
  • RESTART_FAILURE → restart only if exit_code != 0
  • RESTART_NEVER → mark STOPPED
  • Check max_retries → MAINTENANCE if exceeded

Phase 5: main.reef — Event Loop

Purpose: Wire all modules together into the main poll(2) event loop.
Testable on Linux: Partial — poll/signals/sockets yes, contracts no.

Imports

import config
import depgraph
import contract
import supervisor
import socket
import sys.process as process
import sys.signal as signal
import sys.poll as poll
import sys.fd as fd

Main Procedure Flow

  1. Load config: config.load_enabled_services()
  2. Build dependency graph: depgraph.build_graph() + depgraph.topo_sort()
  3. Create service table: supervisor.new_service_table()
  4. Start services tier by tier
  5. Set up event sources:
    • Contract bundle fd: contract.open_bundle()
    • Signal self-pipe: signal.selfpipe_create() for SIGCHLD, SIGTERM, SIGHUP
    • Unix socket: socket.create_socket()
  6. Main loop: poll.poll_wait() with 1s timeout
    • Contract events → supervisor.handle_contract_event()
    • Signals → reap children, shutdown, reload
    • Socket → socket.handle_client()
    • Periodic: check stop timeouts, restart delays

Build Command

reefc build --gc-signals rtmin         # Linux dev
reefc build --gc-signals rtmin -l contract  # Hammerhead production

Phase 6: socket.reef — Unix Domain Socket Server

Purpose: Accept zygctl connections, dispatch commands, send responses.
Testable on Linux: Yes — fully portable.

Imports

import net.unix as unix
import supervisor
import config
import core.str

Protocol

  • Request: COMMAND [ARG]\n (newline-terminated text)
  • Response: Plain text lines, connection closed after response

Commands

Command Description
status List all services with state
status <name> Status of one service
start <name> Start a service (transient)
stop <name> Stop a service
restart <name> Stop then start
enable <name> Create symlink in enabled.d
disable <name> Remove symlink from enabled.d
list List all known service definitions

Functions

  • create_socket(): int — bind and listen on /var/run/zyginit.sock
  • handle_client(server_fd, table) — accept, read, dispatch, respond, close
  • parse_command(input, arg_out): string
  • cmd_status(table, arg): string
  • cmd_start(table, name): string
  • cmd_stop(table, name): string
  • cmd_restart(table, name): string
  • cmd_enable(name): string
  • cmd_disable(name): string
  • cmd_list(table): string
  • destroy_socket() — cleanup

Phase 7: tools/zygctl — CLI Tool

Purpose: Admin CLI that connects to zyginit over Unix domain socket.
Testable on Linux: Yes.

Layout

tools/zygctl/
├── reef.toml            # entry = "src/main.reef", output = "zygctl"
└── src/
    └── main.reef

Imports

import sys.args as args
import net.unix as unix
import core.str

Usage

zygctl status              # list all services
zygctl status <name>       # status of one service
zygctl start <name>        # start a service (transient)
zygctl stop <name>         # stop a service
zygctl restart <name>      # restart a service
zygctl enable <name>       # enable at boot (creates symlink)
zygctl disable <name>      # disable at boot (removes symlink)
zygctl list                # list all service definitions

Implementation

  1. Parse args
  2. For enable/disable: operate locally on symlinks (no daemon needed)
  3. All other commands: connect to /var/run/zyginit.sock, send command, print response

Platform Testing Strategy

Phase Linux Hammerhead (standalone VM)
1 Full test with example TOML Same
2 Full test with mock graphs Same
3 Compile only Full test
4 fork/exec without contracts Full test
5 Poll loop without contracts Full test
6 Full test Same
7 Full test Same

Risk Items

  1. ct_status_read pointer semantics — pointer-to-pointer FFI, prototype on Hammerhead
  2. TOML inline array parsing — stdlib doesn't parse natively, use Coral's proven pattern
  3. Contract event binary format — reading from bundle fd needs Hammerhead struct parsing
  4. String splitting — verify str.split() availability in Reef 0.4.0 stdlib
  5. HashMap with struct values — may need index-based indirection instead of HashMap[ServiceDef]