# 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

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

### Types

```reef
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

```reef
import collections.hashmap as hashmap
import core.str
```

### Types

```reef
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

```reef
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

```reef
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

```reef
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

```reef
// 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

```reef
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

```bash
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

```reef
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

```reef
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]`
