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:
- Vendor zyginit source from this repo into Hammerhead's source tree as part of
hammerhead-userland. - Wire zyginit into Hammerhead's master build, install paths, and packaging.
- Migrate the existing 18 zyginit-converted services (currently in
services/hammerhead/) into Hammerhead's source tree as the starting point. - 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.
- Remove SMF infrastructure (
svc.startd,svc.configd, master restarter, libscf consumers, manifest packaging) fromhammerhead-userland.
The deliverable is one Markdown document. zyginit ships docs and patterns; Hammerhead does the OS-level execution.
2. Non-goals
- No
smf-to-tomlconversion 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.configdever 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.shand callsmf_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:
- configd-coupled daemons silently degrade (iter 1-5, BSD pivot).
- Daemonize handshake (iter 16, supervisor handles it but expect log noise).
- Foreground-flag names vary (
dlmgmtd -d,ipmgmtd -f,sshd -D,syslogd -d,fmd -f). - soconfig at boot — without it,
socket(AF_INET)returns EAFNOSUPPORT. /var/runnot yet tmpfs in zyginit BE — files persist across reboots; follow-up to mount it.- utmpx record positioning —
pututxlineupdates an existing matching record, doesn't append. Live-replace boot-id sentinel must come from/proc/1/start, not utmpx. - Don't
zfs set readonly=onon the live root during shutdown (iter 19-20). - uadmin must not be called from PID 1; fork a child (iter 19-20).
/etc/hostsinstall bug — base ships onlylocalhost, 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:
- Source layout decision. Recommended target path:
usr/src/cmd/init/(BSD-tree convention; same placecmd/init/init.clives in standard illumos). zygctl tousr/src/cmd/zygctl/. sysv-wrapper tousr/src/cmd/sysv-wrapper/. Sync mechanism (git submodule, source snapshot, fetch-and-extract) is the Hammerhead team's call. - 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/ - Build invocation — what the build needs to do:
reefc build -l contract --obj <helpers.o>→ produces/sbin/initreefc buildfor zygctl → produces/sbin/zygctlmakefor sysv-wrapper → produces/sbin/sysv-wrapperplus 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)
- Reef compiler dependency — Hammerhead's build needs
reefc. Either bootstrap from Reef repo or assume a pre-built reefc. Hammerhead team's call. - 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:- Vendor zyginit source into Hammerhead tree
- Wire up the build to produce
/sbin/init - Vendor the existing 18 TOMLs
- Validate: build a Hammerhead BE; boot; verify all 18 services come up (matches current hh-prototest behavior)
- THEN start the per-manifest conversion of the remaining manifests using the decision tree
- 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 ALLsystem/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 currentservices/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. |