# SMF Migration Handoff Document — Design

**Date:** 2026-05-04
**Status:** Approved (brainstorm complete); implementation plan to follow
**Scope:** A single Markdown document at `docs/SMF_MIGRATION.md` that hands off the remaining SMF-removal and zyginit-integration work from the zyginit project to the Hammerhead-project Claude (or Hammerhead human engineers). No tooling. No code.

## 1. Goal

Hand off the remaining SMF removal work to the Hammerhead team (or a Hammerhead-project Claude agent) with everything they need to:

1. Vendor zyginit source from this repo into Hammerhead's source tree as part of `hammerhead-userland`.
2. Wire zyginit into Hammerhead's master build, install paths, and packaging.
3. Migrate the existing 18 zyginit-converted services (currently in `services/hammerhead/`) into Hammerhead's source tree as the starting point.
4. Convert the remaining ~110 SMF manifests using the patterns documented in this work, the existing converted services as references, and project judgment about what Hammerhead actually ships.
5. Remove SMF infrastructure (`svc.startd`, `svc.configd`, master restarter, libscf consumers, manifest packaging) from `hammerhead-userland`.

The deliverable is **one Markdown document**. zyginit ships docs and patterns; Hammerhead does the OS-level execution.

## 2. Non-goals

- **No `smf-to-toml` conversion tool.** A Claude agent doing the conversion in-place can do both the mechanical XML-to-TOML translation AND the per-service judgment that a tool can't. Avoiding the tool also keeps zyginit free of a Python runtime dependency and aligns with the structural rule "DO NOT modify Hammerhead source code" — the Hammerhead Claude does that work in its own project.
- **No per-service detailed catalog with full recommendations.** zyginit doesn't know what Hammerhead ships; per-service decisions stay with the Hammerhead team. The doc provides a coarse one-line classification per manifest and lets the Hammerhead Claude finish each per-service decision with full local context.
- **No format reference duplication.** TOML field-by-field details stay in `man/zyginit.5`. The migration doc references it.
- **No build-system implementation.** The doc describes what the Hammerhead build needs to do; the Hammerhead Claude integrates it with the actual build conventions in their tree.
- **No Coral packaging for zyginit.** zyginit is part of `hammerhead-userland` (BSD-style base system), built into the OS by Hammerhead's master build.
- **No automated catalog maintenance.** The catalog is a snapshot of one Hammerhead manifest tree at one point in time; it is archival once the migration is complete.

## 3. Document structure

The single Markdown file at `docs/SMF_MIGRATION.md`, ~600-800 lines. Top-level sections:

```
1. Handoff Brief                       (~100 lines)
2. Decision Tree                       (~150 lines)
3. Worked Examples                     (~200 lines)
4. Anti-patterns & Gotchas             (~100 lines)
5. Integration with Hammerhead         (~150 lines)
6. Per-Category Guidance               (~100 lines)
7. Catalog Appendix                    (~200 lines, table format)
8. Format Reference Pointer            (~10 lines)
9. Glossary                            (~30 lines)
```

The doc is intended for both human engineers and AI agents working on the Hammerhead source tree. Format optimized for human readability; structured enough that a Claude agent can load it as context and act on it.

The file lives at `docs/SMF_MIGRATION.md` (top-level under `docs/`, alongside `DESIGN.md`). The spec for THIS doc lives at `docs/superpowers/specs/2026-05-04-smf-migration-doc-design.md`. The migration doc itself stays at the top level to signal "this is a real artifact for the Hammerhead team," not a planning artifact.

## 4. Section content

### 4.1 Handoff Brief

Sets up the context for the Hammerhead Claude. Includes:

- What zyginit has done so far (the iter 1-20 work; the contract-readoption feature; the 18 converted services in the boot chain on `hh-prototest`).
- What's left for the Hammerhead team to do (source vendoring, build integration, migrate the 18 existing TOMLs, convert the remaining ~110 manifests, remove SMF infrastructure).
- Coordination points: when to bounce a question back to the zyginit project (e.g., "the existing patterns don't fit this service" → file a zyginit issue; we extend the format/runtime if needed).
- Out-of-scope reminders ("DO NOT modify Hammerhead source from inside zyginit's tree" — this constraint is for the zyginit-project Claude, not the Hammerhead Claude; the Hammerhead Claude WILL modify Hammerhead source — that is its job).
- Done criteria for "SMF removal complete": Hammerhead boots and reaches multi-user without `svc.startd` / `svc.configd` ever being invoked; libscf-only consumers gone or stubbed; manifest infrastructure removed from packaging; existing zyginit-supervised services continue to work.

### 4.2 Decision Tree

The most consequential single piece of the doc — what the Hammerhead Claude leans on for every manifest. Three top-level exits per manifest:

```
Step 1: Should this service exist on Hammerhead at all?
   ├── No (deprecated, replaced)
   │     → DROP. Remove from package; do not write a TOML.
   ├── Yes, but as zports add-on, not base
   │     → DEFER. Out of scope for hammerhead-userland.
   └── Yes, ships in hammerhead-userland → continue to Step 2

Step 2: Is the service tightly coupled to SMF infrastructure
        (svc.configd / libscf / repository_door)?
   ├── Yes, fundamentally requires libscf at runtime
   │     → INVESTIGATE. Find a foreground/standalone mode, or
   │       coordinate with the daemon's upstream to add one.
   ├── Yes, but the daemon has a non-SMF mode flag (-d, -f, -D)
   │     → CONVERT (foreground-mode pattern). See Example 1.
   └── No, or coupling is incidental → continue to Step 3

Step 3: What runs the service today?
   ├── Direct daemon binary, no SMF method script
   │     → CONVERT (full-TOML pattern, simplest case)
   ├── /lib/svc/method/<name> shell script that does setup before exec
   │     ├── Trivial setup → INLINE into TOML's exec.start
   │     ├── Substantial setup (sources lib/svc/share/*.sh) → KEEP
   │       method script as exec.start. See Example 2.
   ├── Oneshot configuration step (mountall, swapadd, dumpadm)
   │     → CONVERT as oneshot service. See services/hammerhead/
   │       root-fs, swap, filesystem.
   └── inetd-managed service
         → DROP unless strong reason to keep. We don't run inetd.
```

Within "CONVERT," a field-by-field translation table maps SMF manifest XML elements to TOML fields:

| SMF concept | TOML field | Notes |
|---|---|---|
| `<service name='X'>` | `[service] name = "X"` | Strip category prefix |
| `<template>` short_description | `[service] description` | Plain string |
| `<service_bundle type='manifest'>` | `[service] type = "daemon\|oneshot\|transient"` | Map by behavior |
| `<exec_method type='start'>` | `[exec] start = "..."` | Replace method-script if Step 2 said foreground-mode |
| `<exec_method type='stop'>` | `[stop] method = "exec"` + `[exec] stop = "..."` | `:kill` in SMF maps to `[stop] method = "contract"` |
| `<dependency type='service'>` | `[dependencies] requires = [...]` | Translate FMRI to bare name; drop SMF-internal deps |
| `<dependency type='path'>` | `[dependencies] requires = ["filesystem"]` | Path deps mostly map to "filesystem is up" |
| `<property_group name='startd'>` `duration` | `[service] type` | transient vs daemon |
| `<property_group name='general'>` `enabled` | not in TOML | Operator decides via symlink in `enabled.d/` |
| Contract behavior | `[contract] param = [...]`, `fatal = [...]` | Defaults usually fine |
| Restart-on-error | `[restart] on = "always\|failure\|never"` | SMF's `ignore_error` maps to `on = "failure"` |

Anti-patterns explicitly called out:
- "Just convert it because it exists" — Step 1 audit is mandatory.
- "Wrap the method script verbatim" — many method scripts source `lib/svc/share/smf_include.sh` and call `smf_present`; wrapping reintroduces configd coupling.
- "Skip Step 2 because it's complicated" — getting Step 2 wrong was the entire iter 1-5 dead end.

### 4.3 Worked Examples

Three examples, each ~60-80 lines, structured identically.

**Example 1: foreground-mode conversion (canonical).** Subject: `dlmgmtd` (`/lib/svc/manifest/network/dlmgmt.xml`). Walks through: original SMF manifest, decision-tree walk (Step 1 keep, Step 2 yes-coupled-but-has-`-d`-flag), the resulting `services/hammerhead/dlmgmtd.toml`, why-we-made-each-choice (the existing comment block on dlmgmtd.toml is the educational artifact), and the iter 11-15 confirmation that `dladm`/`ipadm` work.

**Example 2: SMF method-script wrap.** Subject: `system/cron`. Original manifest with the `<exec_method type='start'>/lib/svc/method/svc-cron start</exec_method>` line. Decision-tree walk (Step 1 keep, Step 2 not coupled, Step 3 method-script trivial). Two valid TOMLs side-by-side: the inlined version vs the wrap version. Show that both are correct; explain when you'd pick which. Show the existing `services/hammerhead/cron.toml` as the production choice.

**Example 3: drop.** Subject: `system/svc/restarter`. The classic must-drop. Original manifest showing FMRI (`svc:/system/svc/restarter:default`) and exec method (`/lib/svc/bin/svc.startd`). Decision-tree walk: Step 1 NO — this IS SMF; zyginit replaces it. Document the cascade: most other manifests reference this in `<dependency>` blocks; when those services get converted, those dependencies are dropped (they were SMF-internal, not real ordering constraints). Other must-drop companions: `svc.configd`, the master restarter framework as a whole.

What's not in the worked examples (but pointed to from elsewhere in the doc):
- Oneshot conversions (root-fs, swap, filesystem) → already in `services/hammerhead/`; doc points there.
- Daemonize-handshake-needing services (syslogd, utmpd) → covered in Anti-patterns rather than as a worked example.
- Network-config-style → already in `services/hammerhead/network.toml`; pointer rather than walk-through.

### 4.4 Anti-patterns & Gotchas

A short section drawn from the iter notes already in memory. Each gotcha gets ~3-4 lines: symptom, cause, fix. Cross-reference into the iter notes (`pid1_boot_iter11_15.md`, etc.) so the Hammerhead Claude can pull more context.

Topics:
1. configd-coupled daemons silently degrade (iter 1-5, BSD pivot).
2. Daemonize handshake (iter 16, supervisor handles it but expect log noise).
3. Foreground-flag names vary (`dlmgmtd -d`, `ipmgmtd -f`, `sshd -D`, `syslogd -d`, `fmd -f`).
4. soconfig at boot — without it, `socket(AF_INET)` returns EAFNOSUPPORT.
5. `/var/run` not yet tmpfs in zyginit BE — files persist across reboots; follow-up to mount it.
6. utmpx record positioning — `pututxline` updates an existing matching record, doesn't append. Live-replace boot-id sentinel must come from `/proc/1/start`, not utmpx.
7. Don't `zfs set readonly=on` on the live root during shutdown (iter 19-20).
8. uadmin must not be called from PID 1; fork a child (iter 19-20).
9. `/etc/hosts` install bug — base ships only `localhost`, not actual hostname; causes 20-second login delays via DNS timeout. Flagged today as upstream Hammerhead bug.

### 4.5 Integration with Hammerhead build

Substantive section. zyginit is part of `hammerhead-userland` (BSD-style base system); Hammerhead's master build pulls and builds zyginit alongside other userland components. zyginit repo continues as upstream development repo; periodic syncs into Hammerhead.

Subsections:

1. **Source layout decision.** Recommended target path: `usr/src/cmd/init/` (BSD-tree convention; same place `cmd/init/init.c` lives in standard illumos). zygctl to `usr/src/cmd/zygctl/`. sysv-wrapper to `usr/src/cmd/sysv-wrapper/`. Sync mechanism (git submodule, source snapshot, fetch-and-extract) is the Hammerhead team's call.
2. **File-by-file mapping** (explicit table):
   ```
   zyginit-repo/src/*.reef                  → usr/src/cmd/init/src/
   zyginit-repo/src/helpers.c               → usr/src/cmd/init/src/
   zyginit-repo/src/contract_linux_stubs.c  → DROP (Hammerhead doesn't need Linux stubs)
   zyginit-repo/services/hammerhead/*       → usr/src/cmd/init/services/
   zyginit-repo/tools/zygctl/src/           → usr/src/cmd/zygctl/src/
   zyginit-repo/tools/zygctl/src/symlink_wrapper.c → same
   zyginit-repo/tools/sysv-wrapper/         → usr/src/cmd/sysv-wrapper/
   zyginit-repo/man/man8/*                  → usr/src/man/man8/
   ```
3. **Build invocation** — what the build needs to do:
   - `reefc build -l contract --obj <helpers.o>` → produces `/sbin/init`
   - `reefc build` for zygctl → produces `/sbin/zygctl`
   - `make` for sysv-wrapper → produces `/sbin/sysv-wrapper` plus argv[0] symlinks (`/sbin/halt`, `/sbin/reboot`, `/sbin/poweroff`, `/sbin/telinit`)
   - Install service TOMLs to `/etc/zyginit/`
   - Install start.sh / stop.sh scripts to `/etc/zyginit/<svc>/`
   - Install man pages
   - Drop `svc.startd`, `svc.configd`, master restarter from the build (the actual SMF removal step)
4. **Reef compiler dependency** — Hammerhead's build needs `reefc`. Either bootstrap from Reef repo or assume a pre-built reefc. Hammerhead team's call.
5. **First migration step (concrete deliverable):** copy the existing `services/hammerhead/*.toml` (and start.sh / stop.sh subdirs) from the zyginit repo into Hammerhead's source tree. This gets the iter 18-20 boot chain into Hammerhead's source as the starting point, before any of the ~110-manifest conversion work begins. Order:
   1. Vendor zyginit source into Hammerhead tree
   2. Wire up the build to produce `/sbin/init`
   3. Vendor the existing 18 TOMLs
   4. Validate: build a Hammerhead BE; boot; verify all 18 services come up (matches current hh-prototest behavior)
   5. THEN start the per-manifest conversion of the remaining manifests using the decision tree
6. **Upstream-sync workflow** — when zyginit ships new features, the Hammerhead team:
   - Re-fetches zyginit repo at a known tag/commit
   - Refreshes the vendored source under `usr/src/cmd/init/`
   - Resolves any local Hammerhead patches that conflict
   - Rebuilds and tests
   - Documents the sync point (commit hash) in Hammerhead's changelog

### 4.6 Per-Category Guidance

Walks the SMF manifest tree top-down, gives recommendations per category. The Hammerhead Claude reads this once for orientation, then drops into the catalog appendix for per-manifest decisions.

Structure mirrors the actual filesystem at `/lib/svc/manifest/`:
- `application/` — mostly Hammerhead-irrelevant; drop or defer to zports.
- `network/` — selective: keep DNS resolver consideration, NFS only if shipped, drop NIS/iSCSI/loadbalancer/SMB.
- `platform/` — keep amd64-specific bits; drop sun4u/sun4v (SPARC; Hammerhead is amd64-only).
- `system/` — keep core (devfs, filesystem, fmd-if-FMA-wanted, ipfilter); DROP ALL `system/svc/*` (this is SMF infrastructure itself).

Each category gets ~3-5 lines: what's there, default recommendation, per-category gotchas. Cross-reference into the catalog appendix for individual manifests.

### 4.7 Catalog Appendix

The 128 manifests as a sortable, scannable Markdown table. Format:

```
| Manifest path | Category | Classification | Note |
|---|---|---|---|
| /lib/svc/manifest/system/cron.xml | system | already-converted | services/hammerhead/cron.toml |
| /lib/svc/manifest/system/svc/restarter.xml | system | drop | This IS svc.startd; we replace it |
| /lib/svc/manifest/system/fm/fmd.xml | system | foreground | fmd -f flag; needs verification |
...
```

Five classifications:
- `already-converted` — TOML exists in current `services/hammerhead/`; just migrate to Hammerhead source tree.
- `drop` — service should not be in Hammerhead at all.
- `foreground` — convert via foreground-mode pattern (decision tree Step 2).
- `method-wrap` — keep the SMF method script as exec.start (decision tree Step 3).
- `needs-investigation` — depends on a Hammerhead-team policy decision.

The "Note" column carries the why — for `already-converted` it's a path pointer; for `drop` a one-line reason; for `foreground` the candidate flag; for `method-wrap` the method script path; for `needs-investigation` the open question.

**Generation effort:** populating the catalog is the substantive zyginit-side work — open each XML, fill in classification + note. Most entries are obvious; a handful need real thought. Output is a static Markdown table.

**Maintenance posture:** the catalog is a snapshot of one Hammerhead manifest tree. As Hammerhead evolves upstream, the catalog drifts. The doc explicitly notes this is a starting-point snapshot, not a living index.

### 4.8 Format Reference Pointer

Single short section: "For TOML field-by-field reference, see `man/zyginit.5`. The format reference is authoritative there; this doc covers patterns and decisions, not syntax."

### 4.9 Glossary

Five-to-ten-line definitions of SMF concepts that show up in the doc but may be unfamiliar to non-illumos hands or AI agents whose training data is light on Solaris:
- FMRI, Manifest, Method, Profile, Repository, Restarter, Contract, libscf, libcontract, BE / Boot Environment, soconfig.

## 5. Audience and writing style

The doc is written for both human engineers and AI agents working on the Hammerhead source tree. Format:

- Markdown, GitHub-flavored, no emojis.
- Headings to two levels (`##`, `###`) for navigation; deeper structure via lists.
- Code blocks (```` ``` ````) for TOML, XML, and shell snippets.
- Cross-references to other files use repo-relative paths (e.g., `services/hammerhead/dlmgmtd.toml`, `man/zyginit.5`, `pid1_boot_iter11_15.md`).
- Each major section ends with a "see also" line pointing to related sections.
- The catalog appendix uses Markdown tables (not flat lists).
- Each subsection starts with a one-sentence purpose statement so readers can skim.

Optimizing for human readability also serves AI agents well; no separate "agent-friendly" tracks.

## 6. Implementation effort

Approximate: 4-6 hours of focused work for a Claude agent to write this doc end-to-end:

- Sections 1, 2, 4, 5, 6, 8, 9 — straightforward writeup of decisions already made; ~2-3 hours.
- Section 3 (worked examples) — requires walking through the actual XML manifests for dlmgmtd, cron, and svc.startd; ~1 hour.
- Section 7 (catalog) — open each of 128 manifests on hh-prototest, classify, fill in notes; ~2-3 hours.

Spec self-review after writing: ~30 minutes.

## 7. Decisions summary

| Decision | Choice | Rationale |
|---|---|---|
| Tooling | None (no `smf-to-toml` translator) | Claude does both mechanical and judgment work in one pass; tool would duplicate the mechanical part and add a Python dependency. |
| Format | Single Markdown doc at `docs/SMF_MIGRATION.md` | Simpler than two coupled docs; one source of truth for the handoff. |
| Depth | Middle ground (~5-7 pages prose + catalog appendix) | Decision tree + 2-3 worked examples + anti-patterns + per-category guidance. Format reference stays in `man/zyginit.5`. |
| Catalog | Coarse classification per manifest (5 classes) | One-line annotations for all 128; per-service detailed decisions stay with Hammerhead team. |
| Tool language | N/A (no tool) | — |
| Integration model | zyginit vendored into `hammerhead-userland` (OpenBSD-style base) | Per user direction; not Coral-packaged; zyginit repo continues as upstream. |
| Audience | Both human engineers and AI agents | Single doc serves both; readability optimized for humans. |
