|
root / docs / superpowers / specs / 2026-05-04-smf-migration-doc-design.md
2026-05-04-smf-migration-doc-design.md markdown 268 lines 19.4 KB

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.