Volume 4 — Cross-Platform System Management — contents
  1. Chapter 1 — Why a Contract Layer
  2. Chapter 2 — The 52 Cross-OS Contracts
  3. Chapter 3 — featurebase and the Per-OS Implementation Pattern
  4. Chapter 4 — RHEL Implementation
  5. Chapter 5 — Ubuntu / Debian Implementation
  6. Chapter 6 — macOS Implementation
  7. Chapter 7 — Windows Implementation
  8. Chapter 8 — UEFI Firmware Implementation
  9. Chapter 9 — Spec-Driven Development for System Administration
  10. Chapter 10 — Building Cross-OS Test Parity

Chapter 9 — Spec-Driven Development for System Administration

Eight chapters walked the contracts and the implementations. This chapter walks the discipline that keeps them aligned: the 11 Implementation Conventions documented at codecraftai-sysman-common/specs/contracts/README.md:75–364. The conventions are what prevent drift across 5 OSes × ~50 features without a code generator.

The volume's previous chapters referenced the conventions piecemeal — Convention 4 (OS-prefixed names), Convention 5 (FAILED with stable token), Convention 11 (Homebrew elevation inversion). This chapter walks all eleven as a unified discipline. By the end, a reader writing a new per-OS implementation knows the rules in detail.

The conventions emerged from real implementation work — specifically the Phase-3 cross-OS push that brought Ubuntu, macOS, and Windows online alongside the original RHEL package. Each convention is a crystallisation of a problem the implementation work uncovered.

Why conventions instead of code

A reader from a heavily-tooled engineering culture might ask: if these are uniform rules across all packages, why not enforce them in code via metaclasses, decorators, or a linter?

Three reasons:

Many of the conventions are naming

Convention 4 (OS-prefixed type names) is enforceable by a linter. But the discipline isn't just the prefix — it's that the prefix appears consistently across types (UbuntuPackageManager, UbuntuPackageActionResult, UbuntuPackageActionStatus). A linter that just checks class-name prefixes misses the cross-type alignment.

Many of the conventions are trade-offs

Convention 7 (reads degrade gracefully — return None / empty / synthetic record vs. raise) gives implementations choice. The discipline is that the implementation picks one and is consistent. Code can't enforce "be consistent in your choice."

The conventions evolve

Phase-3 added Convention 6 (elevation_blocked). A future phase might add Convention 12. Hard-coding the conventions in framework code makes them harder to evolve; documenting them in prose lets them grow.

The eleven conventions

Convention 1 — ICommandRunner is the file-I/O seam

File I/O MAY use IFileOperator if the host package provides one; the established pattern is to route file I/O through ICommandRunner using OS-native file utilities.

The framework has no separate file-operator abstraction. Reading, writing, removing files all goes through the same ICommandRunner that runs commands. On Linux, that means invoking cat / tee / rm / mv. On Windows, it means invoking Get-Content / Set-Content / Remove-Item.

The convention exists because:

  • A separate IFileOperator would duplicate concerns the runner already handles (elevation, dry-run, audit recording).
  • Routing file I/O through the runner means the audit trail (every action records its argv per Convention 2) works for file operations too.
  • Tests that mock the runner automatically also mock the file operations.

A worked example. When a feature needs to write a config file:

# Wrong — bypasses the runner.
with open("/etc/myservice.conf", "w") as f:
    f.write(content)

# Right — routes through the runner per Convention 1.
self._runner.run(
    ["tee", "/etc/myservice.conf"],
    elevated=True,
    stdin=content,
)

The runner-routed version:

  • Goes through the manager's elevation logic.
  • Captures the argv in the action result.
  • Honours dry-run (the runner doesn't invoke when dry_run=True).
  • Routes through SSH when the runner is an SshAgent (so the file write happens on the remote host, not the controller's filesystem).

Convention 2 — command: list[str] | None field on action results

Every per-OS *ActionResult dataclass carries a command field that records the argv that was (or would have been) executed.

The command field on every result enables:

  • Audit logging — what changed and how.
  • Dry-run inspection — see the would-have-been argv.
  • Test verification — assert on the constructed command.
  • Forensic debugging — when a test fails, the command field tells you exactly what ran.

featurebase._dispatch populates this automatically. A manager that doesn't use _dispatch (chapter 4's PackageManager example) has to populate it manually:

result = PackageActionResult(
    status=PackageActionStatus.SUCCESS,
    package_name=name,
    version=installed_version,
    command=command_copy,  # Convention 2
    exit_code=run_result.exit_code,
    stdout=run_result.stdout,
    stderr=run_result.stderr,
)

The field is Optional[list[str]] because SKIPPED results don't have a command (no command was needed).

Convention 3 — requires_reboot: bool on action results that mutate boot-time-only state

Features that mutate boot-time-only state (Windows hostname rename, UEFI capsule-update) carry a requires_reboot: bool = False field set to True when the underlying tool reports the change was staged but a reboot is required.

Some operations succeed in the sense that the tool accepted the change, but the change doesn't take effect until reboot. The result needs to carry both signals: "yes, this succeeded" and "but the system needs to reboot."

Worked example: Windows hostname rename.

# WindowsHostnameManager.rename_computer

def rename_computer(self, new_name: str) -> WindowsHostnameActionResult:
    # ... build PowerShell Rename-Computer argv ...
    result = self._dispatch(
        command=ps_argv,
        new_name=new_name,
    )
    # Rename-Computer reports success but the new name
    # isn't visible until reboot.
    if result.status == WindowsHostnameActionStatus.SUCCESS:
        result.requires_reboot = True
    return result

The test calling rename_computer checks result.requires_reboot and either schedules the reboot or flags the test as needing a manual restart.

Convention 4 — OS-prefixed concrete type names

Already covered extensively (chapters 4 and 5). Recap:

  • Per-OS implementations prefix every public type: UbuntuPackageManager, MacosFirewallManager, WindowsServiceManager.
  • RHEL retains unprefixed names (PackageManager, etc.) for historical reasons; this is the documented exception.
  • The prefix applies to managers, configs, results, statuses, info classes — every public type the package exposes.

Convention 5 — FAILED with a stable detail token for unsupported (host_os, verb) combos

When a feature has no scriptable implementation on a given host OS, the manager returns *ActionResult with status=FAILED and detail set to a machine-readable token like unsupported_on_host_os_darwin.

This replaces the older "raise NotImplementedError" pattern. The convention's value:

  • The contract verb is reachable from any host.
  • The result is machine-discoverable — test code checks result.detail for known tokens.
  • The error path is uniform with other failures — status=FAILED with a structured detail.

Stable tokens documented in the contracts. Examples:

Token Meaning
unsupported_on_host_os_darwin The feature has no macOS implementation.
unsupported_on_host_os_windows No Windows implementation.
ufw_no_zones ufw doesn't support multiple zones.
unsupported_no_mok_concept_macos macOS has no MOK equivalent.
unsupported_runtime_change_macos The change requires recovery mode.

A test checking for any unsupported case can match the unsupported_* prefix. Tests checking for a specific case match the full token.

Convention 6 — elevation_blocked: bool on Windows RunResult

Already covered in chapter 7. Recap:

  • Windows can't drive UAC non-interactively.
  • The Windows runner pre-detects elevation status.
  • When a manager requests elevated=True and the calling process isn't elevated, the runner returns a RunResult with elevation_blocked=True.
  • Manager code propagates the flag onto the *ActionResult.
  • Test code checks result.elevation_blocked and skips with a clear reason.

Convention 7 — Reads degrade gracefully

Read methods MAY raise *QueryError OR return a None / empty / synthetic record. Whichever the per-OS spec picks, it MUST be consistent and documented.

The convention gives implementations flexibility on read-failure semantics but requires consistency within an implementation.

Two acceptable patterns:

Raise-on-error

def get_package_info(self, name: str) -> PackageInfo:
    """
        Returns PackageInfo for the named package.

        :raises PackageQueryError: if the package isn't installed.
    """
    info = self._query(name)
    if info is None:
        raise PackageQueryError(f"Package '{name}' is not installed")
    return info

Return-None-on-not-found

def get_package_info(self, name: str) -> Optional[PackageInfo]:
    """
        Returns PackageInfo for the named package, or None if
        not installed.
    """
    info = self._query(name)
    return info  # may be None

The discipline is that all reads in a single implementation pick the same pattern. A package whose some reads raise and some return None is broken.

The per-OS spec documents which pattern the package uses. Test code is written against the documented choice.

Convention 8 — Per-OS backend enums

Features ship a single abstract concept on the contract (e.g., BackupKind, VirtBackend) that diverges into OS-specific enums in practice.

Some features have an abstract concept (a backup kind, a virtualisation backend) that has different concrete options per OS. The contract defines the abstract concept; the per-OS implementation defines its own enum.

Worked example: virtualization-management ships with a VirtBackend abstract enum on the contract, but each per-OS implementation has its own:

class UbuntuVirtBackend(Enum):
    LIBVIRT_KVM = "libvirt-kvm"
    LIBVIRT_LXC = "libvirt-lxc"
    LXC = "lxc"
    QEMU = "qemu"


class MacosVirtBackend(Enum):
    HYPERVISOR_FRAMEWORK = "hypervisor-framework"
    VIRTUALIZATION_FRAMEWORK = "virtualization-framework"
    PARALLELS = "parallels"
    VMWARE_FUSION = "vmware-fusion"


class WindowsVirtBackend(Enum):
    HYPER_V = "hyper-v"
    VMWARE_WORKSTATION = "vmware-workstation"
    VIRTUALBOX = "virtualbox"

Cross-OS test code that needs to handle the VirtBackend argument has per-OS dispatch:

if isinstance(backend, UbuntuVirtBackend):
    # Ubuntu-specific handling
    ...
elif isinstance(backend, MacosVirtBackend):
    # macOS-specific handling
    ...

The convention acknowledges that some abstractions don't collapse cleanly and provides a structured way to handle them.

Convention 9 — Concrete-implementation naming overrides

The contract uses a clean abstract name (SystemIdentity, MailTransportConfig) while the per-OS implementation uses a name that fits its package (UbuntuHostIdentity, UbuntuMailTransportConfig).

Convention 4 prefixes the implementation types. Convention 9 covers the case where the contract type name doesn't fit the per-OS package's terminology.

Worked example: the contract has a type called SystemIdentity describing the host's identity profile. The Ubuntu implementation calls its corresponding type UbuntuHostIdentity because Ubuntu's terminology calls the machine a host, not a system.

The convention allows this divergence with cross-OS adapter layers translating at the boundary. A test that needs to use the contract's SystemIdentity against multiple OSes either:

  • Writes per-OS code that uses each OS's preferred name.
  • Uses an adapter function that converts between the contract type and the implementation type.

Convention 10 — UEFI managers carry override_host_os

Already covered in chapter 8. Recap:

  • UEFI managers detect host OS at construction.
  • The override_host_os: str | None = None config field forces a specific dispatch path for testing.
  • This is testability infrastructure unique to UEFI's single-tier dispatch architecture.

Convention 11 — Elevation-model inversion

Already covered in chapters 6 and 9. Recap:

  • Some tools (Homebrew on macOS) refuse to run as root.
  • The manager passes elevated=False unconditionally.
  • When the calling process is root, the manager wraps the argv with sudo -u <non_root_user>.
  • The pattern is documented per-feature in the Implementation Drift section of the contract.

Credential-delivery conventions

In addition to the eleven implementation conventions, the contracts document credential-delivery conventions at lines 299–328 of the README.

The three patterns, in order of preference:

1. Stdin (preferred)

result = self._dispatch(
    command=["luksFormat", device_path],
    stdin=passphrase,  # secret stays out of argv
    detail=device_path,
)

Secrets flow through stdin. The RunResult doesn't carry the stdin data, so the command field never contains the secret. The audit trail records the argv (["luksFormat", "/dev/sdb"]) without leaking the passphrase.

2. In-process secure-string (Windows)

$securePassword = ConvertTo-SecureString -String $env:PWD -AsPlainText -Force
Set-LocalUser -Name $username -Password $securePassword

PowerShell has SecureString — an in-memory representation that's not paged to disk and not visible in the process listing. Windows managers route credentials through SecureString when the underlying cmdlet supports it.

3. Argv with redaction (last resort)

# Some tools accept credentials only on argv.
command = ["smbpasswd", "-a", username, "-p", password]
result = self._dispatch(
    command=command,
    detail=username,
    redact_argv_indices=[4],  # mark the password slot for redaction
)

When a tool only accepts credentials on argv, the manager marks the secret-bearing positions so the action result's command field has the secret redacted before recording. The framework's argv-redaction logic replaces the marked slots with "[REDACTED]" in the recorded command.

The convention prefers stdin → SecureString → argv-with- redaction. Argv-with-redaction is acknowledged as the fallback for tools that won't accept the better patterns.

How the conventions are enforced

The conventions aren't enforced by tooling. They're enforced by:

Code review

Every PR that touches a sysman package goes through review. Reviewers cross-reference the change against the conventions document. A convention violation is a review comment.

Per-OS test coverage

The contract's "Tests" section prescribes minimum tests. Tests catch convention violations indirectly:

  • A manager that doesn't populate command (Convention 2) fails its dry-run test (the test asserts result.command == [...]).
  • A manager that doesn't honour dry_run (Convention 1's scope) fails its dry-run test.
  • A manager that elevates reads (against the contract) fails its elevation test.

Tests catch the symptoms of convention violations even when the convention isn't tested directly.

Cross-OS comparisons

When two OSes implement the same feature, comparing their tests catches drift. An RHEL PackageManager.install test that's structurally different from a UbuntuPackageManager.install test is a sign one of them is wrong.

What the conventions give you

A reader who understands the eleven conventions can:

  • Read any sysman feature manager and know what to expect.
  • Add a new feature to an existing OS by following the conventions.
  • Add a new OS implementation by following the conventions
    • the contracts.
  • Spot convention violations during code review.
  • Write tests that exercise the convention's behaviour.

The conventions are the discipline. The framework's 50 contracts × 5 OSes = ~250 implementations stay aligned because the conventions are documented, reviewed, and tested.

What's next

Chapter 10 closes the volume by walking cross-OS test parity — how the suite achieves consistent green-across-OSes test runs at the scale of thousands of tests. The chapter explains how the contracts + featurebase + 11 conventions all compose to make a multi-OS test campaign tractable.