Rail-independence is the plan's spine: fedlearn rails verified NOT landed (no m2/m2-core, no capture/curator CLIs), so the wedge builds against frozen interfaces — m2/market-registry as registry of record, ApplyAdapter seam with local adapter now / m2core-sync stub, schemas frozen here with a fedlearn coordination note. Constitution check passed (1 justified deviation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2.1 KiB
Contract: Apply Adapter (v1, frozen) — the fedlearn integration seam
The ONLY interface through which anything installs a Solution bundle onto a machine. Freezing this seam is what lets the wedge ship before the fedlearn rails land (research.md R5) and swap them in later without touching CLI or Store.
Protocol (Python, cli/src/m2_market/apply/base.py)
class ApplyAdapter(Protocol):
name: str # "local" | "m2core-sync"
def plan(self, bundle: Path, state: InstallState) -> Changeset: ...
def apply(self, changeset: Changeset) -> ApplyResult: ... # idempotent
def verify(self, bundle: Path, state: InstallState) -> bool: ...
def rollback(self, changeset: Changeset) -> None: ... # reversible (constitution VI)
Changeset: declarative list of file placements / config merges / post-install checks derived from the bundle'srecipe.yaml, plus achangeset_hashrecorded in InstallState.applyMUST be idempotent (re-apply of an applied changeset = no-op success) and MUST NOT require elevated fleet-wide operations (canary principle).rollbackrestores pre-apply state from the changeset's recorded backups.
v1 implementations
local (ships in this wedge)
Applies payload/ into the operator's agent-home per recipe.yaml targets
(skills, prompts, configs, scripts), runs recipe post-install checks, records
InstallState. Works identically inside primus and agent-latest desktops (targets are
volume paths, not image paths) — satisfying "runtime-sync is the only universal path" in
spirit until the real one exists.
m2core-sync (stub in this wedge)
Raises RailsNotLanded (CLI exit 6) with pointer to this contract. When fedlearn's
m2-core-sync/m2-core pull --apply lands, this adapter shells to it and merges its
state.json with InstallState. Acceptance for the swap (row 10 of plan sequencing): all
quickstart.md scenarios pass with adapter = m2core-sync unchanged.
Selection
config.toml: apply_adapter = "local" (default in v1). Per-install override
--adapter for testing. The Store never selects an adapter — it inherits the CLI's.